<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="" xml:lang="">
<head>
  <meta charset="utf-8" />
  <meta name="generator" content="pandoc" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes" />
  <meta name="author" content="By Faisal Memon" />
  <title>iOS Crash Dump Analysis</title>
  <style>
    code{white-space: pre-wrap;}
    span.smallcaps{font-variant: small-caps;}
    span.underline{text-decoration: underline;}
    div.column{display: inline-block; vertical-align: top; width: 50%;}
    div.hanging-indent{margin-left: 1.5em; text-indent: -1.5em;}
    ul.task-list{list-style: none;}
    .display.math{display: block; text-align: center; margin: 0.5rem auto;}
  </style>
  <link rel="stylesheet" href="style/gitHubStyle.css" />
  <!--[if lt IE 9]>
    <script src="//cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv-printshiv.min.js"></script>
  <![endif]-->
  
</head>
<body>
<header id="title-block-header">
<h1 class="title">iOS Crash Dump Analysis</h1>
<p class="author">By Faisal Memon</p>
</header>
<nav id="TOC" role="doc-toc">
<ul>
<li><a href="#免责声明">免责声明</a></li>
<li><a href="#前言">前言</a></li>
<li><a href="#致谢">致谢</a></li>
<li><a href="#快速入门">快速入门</a>
<ul>
<li><a href="#故障排除">故障排除</a>
<ul>
<li><a href="#资源缺失问题">资源缺失问题</a></li>
<li><a href="#二进制兼容问题">二进制兼容问题</a></li>
<li><a href="#只有模拟器有的问题">只有模拟器有的问题</a></li>
<li><a href="#特定设备的问题">特定设备的问题</a></li>
<li><a href="#用户设备部署的问题">用户设备部署的问题</a></li>
<li><a href="#特定语言环境的问题">特定语言环境的问题</a></li>
</ul></li>
<li><a href="#对于崩溃的思考">对于崩溃的思考</a>
<ul>
<li><a href="#基于不同语言环境的崩溃">基于不同语言环境的崩溃</a></li>
<li><a href="#地理位置引起的崩溃">地理位置引起的崩溃</a></li>
<li><a href="#总线噪音崩溃">总线噪音崩溃</a></li>
</ul></li>
</ul></li>
<li><a href="#基本概念">基本概念</a>
<ul>
<li><a href="#什么是崩溃">什么是崩溃?</a></li>
<li><a href="#操作系统的规则">操作系统的规则</a>
<ul>
<li><a href="#nil-处理示例">Nil 处理示例</a></li>
<li><a href="#获取-mac-地址示例">获取 MAC 地址示例</a></li>
<li><a href="#获取相机权限示例">获取相机权限示例</a></li>
<li><a href="#经验教育">经验教育</a></li>
</ul></li>
<li><a href="#应用程序的策略">应用程序的策略</a>
<ul>
<li><a href="#什么时候应该主动触发崩溃">什么时候应该主动触发崩溃？</a></li>
<li><a href="#什么时候不应该触发崩溃">什么时候不应该触发崩溃</a></li>
</ul></li>
<li><a href="#工程指导">工程指导</a>
<ul>
<li><a href="#单元测试获取-mac-地址">单元测试获取 MAC 地址</a></li>
<li><a href="#ui-tests-测试访问相机">UI Tests 测试访问相机</a></li>
</ul></li>
</ul></li>
<li><a href="#工具">工具</a>
<ul>
<li><a href="#概览">概览</a></li>
<li><a href="#逆向工程">逆向工程</a></li>
<li><a href="#class-dump-工具">Class Dump 工具</a></li>
<li><a href="#第三方工具手机崩溃报告">第三方工具手机崩溃报告</a></li>
</ul></li>
<li><a href="#xcode-内置工具">Xcode 内置工具</a>
<ul>
<li><a href="#xcode-诊断设置">Xcode 诊断设置</a>
<ul>
<li><a href="#执行方法">执行方法</a></li>
<li><a href="#分析方法">分析方法</a></li>
<li><a href="#方法论">方法论</a></li>
</ul></li>
<li><a href="#行百里者半九十">行百里者半九十</a></li>
</ul></li>
<li><a href="#混合环境">混合环境</a>
<ul>
<li><a href="#项目结构">项目结构</a></li>
<li><a href="#范式">范式</a></li>
<li><a href="#问题">问题</a></li>
<li><a href="#解决方案">解决方案</a>
<ul>
<li><a href="#stl-解决方案">STL 解决方案</a></li>
<li><a href="#外观模式解决方案">外观模式解决方案</a></li>
</ul></li>
<li><a href="#经验教训">经验教训</a></li>
</ul></li>
<li><a href="#符号化">符号化</a>
<ul>
<li><a href="#构建过程">构建过程</a></li>
<li><a href="#构建设置">构建设置</a></li>
<li><a href="#关注开发环境的崩溃">关注开发环境的崩溃</a></li>
<li><a href="#dsym-结构">DSYM 结构</a></li>
<li><a href="#着手符号化">着手符号化</a></li>
<li><a href="#逆向工程方法">逆向工程方法</a></li>
</ul></li>
<li><a href="#崩溃报告">崩溃报告</a>
<ul>
<li><a href="#系统诊断">系统诊断</a>
<ul>
<li><a href="#提取系统诊断信息">提取系统诊断信息</a></li>
</ul></li>
<li><a href="#ios-崩溃报告导览">iOS 崩溃报告导览</a>
<ul>
<li><a href="#ios-崩溃报告-header-部分">iOS 崩溃报告 Header 部分</a></li>
<li><a href="#ios-崩溃报告-date-和-version-部分">iOS 崩溃报告 Date 和 Version 部分</a></li>
<li><a href="#ios崩溃报告异常部分">iOS崩溃报告异常部分</a></li>
<li><a href="#ios-崩溃报告已过滤的-syslog-部分">iOS 崩溃报告已过滤的 <code>syslog</code> 部分</a></li>
<li><a href="#ios-崩溃报告中的异常回溯部分">iOS 崩溃报告中的异常回溯部分</a></li>
<li><a href="#ios-崩溃报告线程部分">iOS 崩溃报告线程部分</a></li>
<li><a href="#ios-崩溃报告线程状态部分">iOS 崩溃报告线程状态部分</a></li>
<li><a href="#ios-崩溃报告的-binary-images-部分">iOS 崩溃报告的 Binary Images 部分</a></li>
</ul></li>
<li><a href="#macos-崩溃报告导览">macOS 崩溃报告导览</a>
<ul>
<li><a href="#macos-崩溃报告-header-部分">macOS 崩溃报告 Header 部分</a></li>
<li><a href="#macos-崩溃报告-date-和-version-部分">macOS 崩溃报告 Date 和 Version 部分</a></li>
<li><a href="#macos-持续时间部分">macOS 持续时间部分</a></li>
<li><a href="#macos崩溃报告系统完整性部分">macOS崩溃报告系统完整性部分</a></li>
<li><a href="#macos-崩溃报告的异常部分">macOS 崩溃报告的异常部分</a></li>
<li><a href="#macos-崩溃报告线程部分">macOS 崩溃报告线程部分</a></li>
<li><a href="#macos-崩溃报告线程状态部分">macOS 崩溃报告线程状态部分</a></li>
<li><a href="#macos-崩溃报告-binary-images-部分">macOS 崩溃报告 Binary Images 部分</a></li>
<li><a href="#macos-崩溃报告修改摘要">macOS 崩溃报告修改摘要</a></li>
<li><a href="#macos-崩溃报告虚拟内存部分">macOS 崩溃报告虚拟内存部分</a></li>
<li><a href="#macos崩溃报告系统配置文件部分">macOS崩溃报告系统配置文件部分</a></li>
</ul></li>
<li><a href="#优先考虑自己的问题">优先考虑自己的问题</a>
<ul>
<li><a href="#根据影响确定优先级">根据影响确定优先级</a></li>
<li><a href="#根据截止日期确定优先级">根据截止日期确定优先级</a></li>
<li><a href="#根据趋势确定优先级">根据趋势确定优先级</a></li>
</ul></li>
<li><a href="#说明问题">说明问题</a></li>
<li><a href="#指出问题">指出问题</a>
<ul>
<li><a href="#如何提问">如何提问</a></li>
</ul></li>
<li><a href="#问题范例">问题范例</a>
<ul>
<li><a href="#cameraapp-what-is-is-not-示例">CameraApp What Is / Is Not 示例</a></li>
<li><a href="#imac-where-is-is-not-示例">iMac Where Is / Is Not 示例</a></li>
<li><a href="#数据库应用-when-is-is-not-示例">数据库应用 When Is / Is Not 示例</a></li>
<li><a href="#游戏应用-extent-is-is-not-示例">游戏应用 Extent Is / Is Not 示例</a></li>
</ul></li>
<li><a href="#macbook-pro-t2-的问题">2018 MacBook Pro T2 的问题</a>
<ul>
<li><a href="#故障分析">故障分析</a></li>
</ul></li>
</ul></li>
<li><a href="#siri崩溃">Siri崩溃</a>
<ul>
<li><a href="#我们为什么要关注-siri-崩溃">我们为什么要关注 Siri 崩溃？</a></li>
<li><a href="#崩溃报告-1">崩溃报告</a></li>
<li><a href="#崩溃详情">崩溃详情</a></li>
<li><a href="#使用我们的工具">使用我们的工具</a></li>
<li><a href="#软件工程见解">软件工程见解</a></li>
<li><a href="#经验教训-1">经验教训</a></li>
</ul></li>
<li><a href="#运行时崩溃">运行时崩溃</a>
<ul>
<li><a href="#ios-uikit-outlets">iOS UIKit Outlets</a></li>
<li><a href="#所有权规则">所有权规则</a></li>
<li><a href="#解包-nil-可选类型的崩溃报告">解包 nil 可选类型的崩溃报告</a></li>
<li><a href="#释放正在使用的信号量">释放正在使用的信号量</a>
<ul>
<li><a href="#释放信号量的崩溃示例">释放信号量的崩溃示例</a></li>
<li><a href="#错误的信号量代码">错误的信号量代码</a></li>
<li><a href="#使用特定于应用程序的崩溃报告信息">使用特定于应用程序的崩溃报告信息</a></li>
<li><a href="#经验教训-2">经验教训</a></li>
</ul></li>
</ul></li>
<li><a href="#错误的内存崩溃">错误的内存崩溃</a>
<ul>
<li><a href="#一般原则">一般原则</a></li>
<li><a href="#段冲突-segv崩溃">段冲突 （SEGV）崩溃</a>
<ul>
<li><a href="#fud-崩溃">fud 崩溃</a></li>
<li><a href="#leakagent-崩溃">LeakAgent 崩溃</a></li>
</ul></li>
<li><a href="#总线错误sigbus崩溃">总线错误（SIGBUS）崩溃</a>
<ul>
<li><a href="#xbmc-崩溃">xbmc 崩溃</a></li>
<li><a href="#jablotron-崩溃">Jablotron 崩溃</a></li>
</ul></li>
</ul></li>
<li><a href="#应用程序中止崩溃">应用程序中止崩溃</a>
<ul>
<li><a href="#一般原则-1">一般原则</a></li>
<li><a href="#类型混淆">类型混淆</a>
<ul>
<li><a href="#从配置文件中获取-nsdata-对象">从配置文件中获取 NSData 对象</a></li>
<li><a href="#反序列化崩溃报告">反序列化崩溃报告</a></li>
</ul></li>
<li><a href="#非数字-错误">“非数字” 错误</a></li>
</ul></li>
<li><a href="#资源崩溃">资源崩溃</a>
<ul>
<li><a href="#唤醒崩溃异常">唤醒崩溃异常</a></li>
<li><a href="#设备过热崩溃">设备过热崩溃</a></li>
</ul></li>
<li><a href="#应用程序终止崩溃">应用程序终止崩溃</a>
<ul>
<li><a href="#死锁崩溃">死锁崩溃</a></li>
<li><a href="#不安全的绘制崩溃">不安全的绘制崩溃</a></li>
</ul></li>
<li><a href="#内存诊断">内存诊断</a>
<ul>
<li><a href="#内存分配基础">内存分配基础</a></li>
<li><a href="#address-sanitizer">Address Sanitizer</a></li>
<li><a href="#内存冲突示例">内存冲突示例</a></li>
<li><a href="#堆内存释放后使用示例">（堆内存）释放后使用示例</a></li>
<li><a href="#内存管理工具">内存管理工具</a>
<ul>
<li><a href="#guard-malloc-工具">Guard Malloc 工具</a></li>
<li><a href="#malloc-scribble">Malloc Scribble</a></li>
<li><a href="#zombie-objects僵尸对象">Zombie Objects（僵尸对象）</a></li>
<li><a href="#malloc-堆栈">Malloc 堆栈</a></li>
<li><a href="#动态链接器api的用法">动态链接器API的用法</a></li>
<li><a href="#动态库加载">动态库加载</a></li>
</ul></li>
</ul></li>
<li><a href="#参考书目">参考书目</a></li>
</ul>
</nav>
<h1 id="免责声明">免责声明</h1>
<p>Copyright Faisal Memon 2018。保留所有权利。</p>
<p>本书按“原样”提供，保证没有任何形式的明示或暗示，包括但不限于保证对适销性，针对特定目的的适用性或不侵权的暗示。</p>
<p>本书可能包含技术上的不准确性或印刷错误（笔误）。作者会定期进行更改并修正本书。这些修改将并入本书的新版本中。</p>
<p>Apple 没有明示或暗示地认可这项工作。本书中的资源是根据公共信息和二进制文件或 Apple 软件工具提供的资料确定的。</p>
<p>作者作为雇员或所有者，在过去或未来的公司或机构中所担任的任何职位，并未得到这些实体公司的任意明示或暗示。</p>
<p>已尽一切努力在本文中标识商标用语。 如果有错误或遗漏，请联系作者。到目前为止，我们已经认可以下商标：</p>
<h1 id="前言">前言</h1>
<p>这本书源于20世纪90年代后期的灵感。</p>
<p>很难描述当下的满足感。互联网是下一次工业革命。投资者将其视为投资的最佳场所。硬件，软件和服务都经历了类似寒武纪多样性和创新的爆炸式增长。</p>
<p>我发现自己处于时代的中心。我最近刚加入了 Sun Microsystems。所以，如果你对有一个不错的注意并想开始在网络上给为你的客服提供服务，那么 Sun Microsystems 值得依赖，必不可少。这些服务器你可以自己处理或由托管服务商来处理。</p>
<p>那时的计算机技术已经得到了长足的发展，但是现有的解决方案已经在新方向和网络上得到使用。斯坦福大学科研中心基于 Unix 系统研制的 SunOS 操作系统现在运行在 E-Bay 上，并且运行的很好。</p>
<p>我们有一个电子屏幕，显示了关键客户的系统运行状态。沙特阿美公司的红色灯一直亮着，以至于我们怀疑这是否是董事会本身的错。</p>
<p>我在 Sun 的第一天有些失落。我甚至都没有自己的隔间。我的办公桌看起来像是学校的书桌。我的键盘有几个故障或无效的按键。第一天我坐在一个巨大的办公室（”立方体农场“）的一个角落，却不清楚到底是哪个角落。午餐后，我走了很长一段路后回到了我角落的办公桌。</p>
<p>让我惊讶的是，大约 1/4 之一的开发者的办公桌上都放了一本书。书的封面上写着，“Panic!”<span class="citation" data-cites="panicbook">(<em>Panic! Unix System Crash Dump Analysis</em> 1995)</span>。这是一本关于 SunOS crash dump 分析的书。</p>
<p>当我拥有了自己的隔间并解释了我的同事之后，我注意到那些带着 《Panic!》的工程师们。这本书似乎在处理客户回馈的某些底层问题方面具有额外的优势。总的来说，他提升了 Sun 公司客服中心解决 IQ 问题的效率。</p>
<p>在 Sun，有一种深厚的学习文化。我们经常接受如此广泛的培训和支持，通常情况下，我们每年要进行七门课程，每门课程周期为一周。</p>
<p>一切都很好，直到那一堂课。它被称为分析故障排除（ATS）。这在客服中心引起了极大的争议。这是解决问题的一种正式方法。这门课并没有教会你答案是什么，而是确保你能够提出正确的问题。事实证明，在我们最棘手的问题上，我们缺少提出正确问题的能力。</p>
<p>这是生产力再次迈出的重要一步。但是，有些工程师非常不合时宜，非常挑剔。事实证明，这些技术只是经验丰富的工程师在实践过程中所学到的，他们并不希望任何人毫不费力的得到。</p>
<p>有一天，Chris Drake 来到了我们的办公室。他是与 Kimberley Brown 合作编写《Panic!》一书的 x86 架构专家。他们安排了一个研讨会来指导我们关于 x86 架构上的 SunOS 崩溃。在 Linux 和 GNU/Linux 系统显著崛起之前，这在当时是一件新鲜事物。</p>
<p>我记得有一次，在一节操作系统课上，我环顾整个教室。我注意到教室里到处都是 Sun Microsystems 公司的设备；我凝视着 Sun 的标志，梦想有一天可以在那里工作。梦想实现了。因此，在有关 x86 紧急情况的研讨会上，我有了另一个想法。有一天我要写一本书。这本书将完全集中在讨论单个技术问题上。这本书将记载着我在职业生涯中获得的经验。这就是你现在阅读的这本书。</p>
<h1 id="致谢">致谢</h1>
<p>在此感谢我的同事们对我编写这本书提供的帮助和支持。</p>
<p>感谢哪些被慷慨提供的开源工具，使得本书的信息汇总工作成为一种可能，特别是 <code>pandoc</code> ，它让我更愉快的编写本书。</p>
<p>最后，我要感谢支持我的家庭成员，当我因为在书房中闭关而缺席大部分的家庭活动。谢谢，Junghyun 和 Christopher。 # 介绍</p>
<p>本书填补了当崩溃发生时应用程序开发人员与开发平台之间出现的空白。应用程序开发人员主要想的是高级概念和抽象话。当应用发生崩溃时，现实会把你粗鲁的拉到底层架构、指针和原始数据的 UNIX 世界中。</p>
<p>我们的焦点只在 Apple 的生态系统。</p>
<p>我们的讨论涵盖了 iOS、macOS、tvOS、watchOS和BridgeOS这些平台，ARM 架构和 C（核心框架）、Objective-C、Objective-C++ 和Swift等多种语言。现实世界中的应用程序最终往往会成为由更安全的Swift语言和旧技术的混合体。</p>
<p>我们假设您至少具有 iOS 编程和软件工程的入门知识，并且可以在 Mac 上运用 Xcode 软件。</p>
<p>我们将通过三种不同的观点来统一我们对待问题的方法，让您能全面、稳定的了解情况以及如何解决问题。</p>
<p>首先，我们将会为您提供一份如何使用 Apple 提供的出色的自带工具的 <code>HOW-TO</code> 指南。</p>
<p>其次，我们旨在讨论一个防止和解决崩溃的软件工程概念。</p>
<p>最后，我们提供一种正式的解决问题的方法，但该方法适用于崩溃分析。</p>
<p>编程文献全面分析了软件工程概念，而 Apple 通过 Guides 和 WWDC 视频介绍了其 crash dump 工具。</p>
<p>在软件工程界一般很少讨论如何去解决正式的问题，可能是因为解决问题被视作软件工程师的一种能力。它被认作是一门研究后增加”自身“能力的学科，这些能力似乎是为了从其他人群中区分出”有技术头脑“的人。</p>
<p>我们的目标并不是羞于重复那些我们在其他地方看到过或阅读过的知识，而是采取一种统一的方式来解释整个观点。 为什么 crash dump 分析如此困难的原因在于，它假定工程师具备大量的背景知识，以便腾出空间专注于特定工具或崩溃报告的具体细节。这会使得工程师出现认知障碍，本书就是要克服这种障碍。</p>
<p>作为本书的补充，还会有一个资源网站，用于结合书本内容一起使用，以便您可以自己设置并运行示例项目然后进行试验。本书最后的所有参考文献都收录在书目章节中。例如，您可以在其中找到资源的URL。</p>
<p>支持该书的GitHub网站位于@icdabgithub</p>
<h1 id="快速入门">快速入门</h1>
<p>当近期的代码修改导致应用程序发生崩溃时，我们可以很容易的对崩溃进行分析然后找到相关代码并进行修改。但是通常，崩溃只是由于操作环境的变化而出现。那可能是最烦人的。例如，应用程序在办公室中正常运行，但在客户站点崩溃。我们没有时间去探究为什么崩溃，但需要快速解决或提出解决方法。当探索一个新项目时，会出现另一个常见的问题场景。 在这里，我们没有使用代码库的经验，但是在编译和运行应用程序后立即会遇到崩溃问题。</p>
<p>在本章中，我们将探讨由于操作环境的变化而导致崩溃的可能原因。无需深入分析手头问题的细节就可以解决许多问题。 实际上，有时我们只需在取得进展的同时进行回顾，便可以追溯到根本原因。</p>
<h2 id="故障排除">故障排除</h2>
<h3 id="资源缺失问题">资源缺失问题</h3>
<p>有些时候，我们的应用程序由于资源缺失问题而在启动时崩溃。</p>
<p>我们应该尝试去编译运行项目中的所有的Xcode Target。有时候，某个特定的 Target，会成为该项目整体环境不可获取的一部分如果是这样，我们可以做一个注释，以便稍后解决这些问题。</p>
<h3 id="二进制兼容问题">二进制兼容问题</h3>
<p>由于二进制兼容的问题，有些时候，我们的应用程序会在启动时发生崩溃。</p>
<p>如果我们刚刚更新了我们的Xcode，或者在编译好我们工程的同时拉代码进行了更新。我们可以使用 <code>Option-Command-Shift-K</code> 命令编译过程中产生的中间文件，目标文件及可执行文件，使得项目回到没有编译之前的状态，然后重新编译。</p>
<blockquote>
<p>一般来说我们可以直接删除 <code>~/Library/Developer/Xcode/DerivedData</code> 达到近似效果</p>
</blockquote>
<h3 id="只有模拟器有的问题">只有模拟器有的问题</h3>
<p>有些时候，我们的应用程序只在模拟器上发生崩溃。</p>
<p>这时候，我们应该尝试模拟器的 <code>Hardware-&gt;Reset</code> 功能对内容和功能进行重置。我们可以尝试使用 iPad 模拟器来替代 iPhone模拟器，反之亦然。示例项目通常用于解释特定技术，而不会去考虑产品化或着通用性。</p>
<h3 id="特定设备的问题">特定设备的问题</h3>
<p>有些时候，我们的应用程序只会在客户设备上发生崩溃。</p>
<p>我们可以检查 <code>Wi-Fi</code> 设置或尝试将 iPad 热点连接到 iPhone。在办公室/家庭环境中开发我们的应用程序时，有时我们会忽略诸如连接或延迟之类的网络问题。如果这就是问题所在，我们应该记录下来以便尝试并修复这些问题。</p>
<h3 id="用户设备部署的问题">用户设备部署的问题</h3>
<p>有些时候，我们的应用程序仅在客户设备上发生崩溃。</p>
<p>当我们将电脑与用户的设备相连时，我们可能正在进行 <code>Debug</code> 环境的部署。这意味着推送通知的 <code>tokens</code> 将是开发环境而不是生产环境的推送 <code>tokens</code>。这同时也意味着资源的访问授权（例如，对 <code>Camera</code> 相机功能的授权）不再有效，因为它们可能已经通过应用程序的 TestFlight 版本或先前的App Store版本（生产版本）获得了批准。</p>
<p>我们应该尝试通过 <code>Command</code> -&lt; 选择左侧面板中的 <code>Run</code>，右侧面板中的 <code>Info</code> 选项，<code>Build Configuration</code> 设置 <code>Release</code>（不是 <code>Debug</code>）来切换部署配置。 我们还应手动检查 <code>iPad/iPhone</code> 设置中的任何资源访问授权。</p>
<h3 id="特定语言环境的问题">特定语言环境的问题</h3>
<p>有时候，客户设备的特定的语言环境会导致崩溃的发生</p>
<p>错误的语言环境中可能会缺失某些资源文件。此外，处理语言环境充斥着毫无征兆的特殊情况。我们应该尝试将区域设置暂时更改为已知的语言区域。 当你回归问题时不要忘记做好笔记。</p>
<blockquote>
<p>不同地区的用户，NSCalendar 默认的 firstWeekday 的值是不一致的，这个基于当地的习惯。如果你的 APP 的受众更为广泛，请注意这一点</p>
</blockquote>
<h2 id="对于崩溃的思考">对于崩溃的思考</h2>
<p>从上面的例子中得到的教训就是我们需要在更广泛的背景下去思考我们的代码。我们应该考虑应用程序的运行环境。 包括：</p>
<ul>
<li>编译后的代码</li>
<li>代码模块之间的二进制不兼容性（不同的语言版本，编译器和工具链）</li>
<li>捆绑或下载到应用程序中的资源文件</li>
<li>构建配置（例如 <code>Release</code> 或 <code>Debug</code>）</li>
<li>网络环境，可用性/延迟/速度</li>
<li>应用程序的允许使用的权限</li>
<li>应用程序被拒绝的权限（在移动设备管理安全环境中）</li>
<li>平台变种</li>
<li>方向</li>
<li>前后端不同的操作模式</li>
<li>硬件性能(旧的慢速硬件与更快的新设备)</li>
<li>硬件组件(GPU，内存，CPU，配件等)</li>
<li>地理位置相关问题</li>
<li>区域问题(语言环境等)</li>
<li>存在诊断设置</li>
<li>存在调试器或分析器</li>
<li>目标设备的操作系统版本</li>
</ul>
<p>作为树立正确的思路以解决应用程序崩溃的第一步，我们有必要解决上述每个操作环境差异，并试着记下这种差异是否会导致我们知道或怀疑可能发生的崩溃。这告诉我们，崩溃更多是关于<strong>环境</strong>而不是<strong>源代码</strong>。另一个次要的见解是，我们越能够根据特定的环境差异生成一系列假设，我们就能更容易、更快地找到其他人看起来很神秘的崩溃的根本原因，而且我们几乎是神奇的想出了问题可能出在哪里的建议。</p>
<p>以下是民间传说中的一些奇怪的信息技术崩溃案例，以激发我们的欲望并让我们展开思考：</p>
<h3 id="基于不同语言环境的崩溃">基于不同语言环境的崩溃</h3>
<p>俄语语言环境在日期处理期间导致崩溃。</p>
<p>这是因为 <code>1984-04-01</code> 被用来当做哨兵日期。但是，在俄罗斯是没有这样的日期/时间，因为在俄罗斯当天是没有午夜的，即没有 <code>1984-04-01 00:00:00</code>。这是因为俄罗斯的夏令时开始于当天的 <code>1</code> 点钟。</p>
<p>这是在 WecudosPro iPad 应用程序开发期间在俄罗斯进行测试时看到的</p>
<h3 id="地理位置引起的崩溃">地理位置引起的崩溃</h3>
<p>一台计算机在每天的不同时间都会发生崩溃。</p>
<p>这个问题实际上是因为这台计算机被放置在一个有船只经过的河口旁边的窗户。在涨潮时，一艘军舰将驶过，其雷达将破坏电子设备从而导致计算机发生崩溃。</p>
<p>在 Kepner-Tregoe 正式的问题解决培训会上，这个民间传说故事被告知给在英国的 Sun Microsystems 客服中心的工程师们。</p>
<h3 id="总线噪音崩溃">总线噪音崩溃</h3>
<p>当计算机同时承受较大的网络负载和磁盘负载时，系统会发生崩溃。</p>
<p>这个奔溃是由于磁盘损坏造成的。每64字节的内存中都会出现一个零。它是计算机的缓存行大小。由于内存板接线错误，导致旁边的磁盘带状电缆在64字节边界处产生噪音。</p>
<p>这是在 Sun Volume Systems Group 计算机的早期原型中看到的。</p>
<h1 id="基本概念">基本概念</h1>
<h2 id="什么是崩溃">什么是崩溃?</h2>
<p>我们的计算机内部是一个操作环境。它包括一个或多个正在运行的操作系统以及应用程序软件。两者的区别在于，操作系统软件在比应用程序软件（用户模式）更高的 CPU 权限（内核模式）下运行。</p>
<p>我们通常理解为我们的应用软件的基本概念模型是位于一个操作系统上，而操作系统本身又是位于硬件上。</p>
<p>但是，现代计算机系统具有多个协作子系统。例如，配备 TouchBar 的MacBook Pro即具有主操作系统macOS，同时也有支持处理 TouchBar 界面、磁盘加密 和 “Hey Siri” 的 Bridge OS。我们计算机中的多媒体和网络芯片是高级组件，可以在其上运行自己的实时操作系统。我们的 Mac 软件只是 macOS 上运行的众多应用程序之一。</p>
<p>应用程序崩溃是操作环境响应我们在应用程序中所做的（或未完成的）某些操作，这些操作违反了我们所运行的平台的某些规则。</p>
<p>当操作系统检测到系统中存在问题时，它可能会自行崩溃。 这称为 <code>kernel panic</code>。</p>
<h2 id="操作系统的规则">操作系统的规则</h2>
<p>操作环境通过某些规则来保障用户的环境安全性、数据安全性、性能和隐私性。</p>
<h3 id="nil-处理示例">Nil 处理示例</h3>
<p>Apple 生态系统的新手常常惊讶地发现，Objective-C 允许我们向 nil 对象发送消息。 它默默地忽略了失败的调用。 例如， 以下方法可以运行正常。</p>
<pre><code>- (void)nilDispatchDoesNothing
{
    NSString *error = NULL;
    assert([error length] == 0);
}</code></pre>
<p>Objective-C 运行时的开发者做出了一个判断，并认为应用程序最好忽略这些问题。</p>
<p>但是，如果我们引用了一个 C 指针，应用程序就会发生崩溃。</p>
<pre><code>void nullDereferenceCrash() {
    char *nullPointer = NULL;
    assert(strlen(nullPointer) == 0);
}</code></pre>
<p>操作系统的开发者为了避免对空指针地址或更底层的内存地址的非法访问，所以中断了应用程序 。</p>
<p>操作系统将此内存区域留出，因为它表示未正确设置对象或数据结构的编程错误。</p>
<p>当出现问题时，我们并不总是会崩溃。 只有它违反运行环境的规则，应用程序才会发生崩溃。</p>
<h3 id="获取-mac-地址示例">获取 MAC 地址示例</h3>
<p>参考获取 iPhone 的的 MAC 的示例。媒体访问控制（MAC）地址是分配给网卡的唯一识别码，用来允许机器在通信栈的数据链路层进行没有重复地相互通信。</p>
<p>在 iOS 7 之前，MAC 地址不被视为敏感 API。因此，使用 <code>sysctl</code> API 请求 MAC 地址会给出真实地址。要查看此操作，请参阅 <code>icdab_sample</code> 应用程序。</p>
<p>不幸的是，此 API 作为跟踪用户的一种方式而被开发者滥用 ，这违反了隐私协议。因此，Apple 在 iOS 7 中引入了一项新的方式，API 始终会返回固定的 MAC 地址。</p>
<p>当调用 <code>sysctl</code> API 时，Apple 本来可以选择使我们的应用程序崩溃。但是，<code>sysctl</code> 是一种通用的底层调用，它可用于其他有效目的。因此，iOS 设置的策略是在请求时返回固定的 MAC 地址 <code>02:00:00:00:00:00</code>。</p>
<h3 id="获取相机权限示例">获取相机权限示例</h3>
<p>现在，让我们考虑使用相机拍摄照片的情况。</p>
<p>在 iOS 10 中，当我们的应用程序需要使用设备相机（隐私且敏感）功能时，我们需要定义一段阅读性良好的授权文案，以便用户理解并给予相机的访问权限。</p>
<p>即便我们没有在 <code>Info.plist</code> 中为 <code>NSCameraUsageDescription</code> 定义授权描述，以下代码的判断结果还是正确的，系统会尝试弹出相册选择器。</p>
<pre><code>if UIImagePickerController.isSourceTypeAvailable(
      UIImagePickerControllerSourceType.camera) {
      // Use Xcode 9.4.1 to see it enter here
      // Xcode 10.0 will skip over
      let imagePicker = UIImagePickerController()
      imagePicker.delegate = self
      imagePicker.sourceType =
       UIImagePickerControllerSourceType.camera
      imagePicker.allowsEditing = false
      self.present(imagePicker, animated: true, completion: nil)
      }</code></pre>
<p>然而，当我们使用Xcode 9.4.1版本运行上面的代码时，我们在控制台会看到以下崩溃的描述性信息：</p>
<pre><code>2018-07-10 20:09:21.549897+0100 icdab_sample[1775:15601294]
 [access] This app has crashed because it attempted to access
  privacy-sensitive data without a usage description.  
  The app&#39;s Info.plist must contain an NSCameraUsageDescription
   key with a string value explaining to the user how the app
   uses this data.
Message from debugger: Terminated due to signal 9</code></pre>
<h3 id="经验教育">经验教育</h3>
<p>请注意这里的对比。这两种情况下我们都调用了涉及隐私敏感的API。但是在相机的案例中，Apple 选择了让应用程序崩溃而不是谈一个弹框给出警告，或者是返回一个错误的值来表明源类型不可用。</p>
<p>这似乎是一个苛刻的设计选择。当用户在 Xcode 10.0（提供了 iOS 12 的SDK） 进行操作时， 这个调用相机授权的 API 有了不一样的表现。当由于未在 <code>Info.plist</code> 中定义授权描述是，判断相机是否可用API会返回 <code>false</code>。</p>
<p>这强调了涉及两个实体的意义，即程序和操作环境（包括其策略）。拥有正确的源代码并不能保证程序能够正常运行。 当我们的应用程序发生崩溃时，我们需要考虑操作环境以及代码本身。</p>
<h2 id="应用程序的策略">应用程序的策略</h2>
<p>我们正在编写的应用程序也可以主动引发崩溃。这通常通过我们的代码中的断言调用来完成的。任何断言的失败，这些调用都会要求操作环境立即终止我们的应用程序。 然后操作环境就终止了我们的应用程序。 那么在崩溃报告中我们会得到这样一个类型:</p>
<p><code>Exception Type:  EXC_CRASH (SIGABRT)</code></p>
<p>表明应用程序在第一时间主动触发崩溃</p>
<h3 id="什么时候应该主动触发崩溃">什么时候应该主动触发崩溃？</h3>
<p>我们可以遵循与操作环境类似的标准来制定我们应用的崩溃策略。</p>
<p>如果我们的代码检测到数据的完整性存在问题，我们可能会触发崩溃以防止进一步的数据损坏。</p>
<h3 id="什么时候不应该触发崩溃">什么时候不应该触发崩溃</h3>
<p>如果问题直接来自某些 IO 问题（例如文件或网络访问）或某些人为的输入问题（例如错误的日期值）那么我们不应改触发崩溃。</p>
<p>作为应用程序开发人员，我们的工作是保护系统的底层部分免受现实世界中存在的不可预测性的影响。通过日志记录，错误处理，用户警报和 IO 重试，可以更好地解决此类问题。</p>
<h2 id="工程指导">工程指导</h2>
<p>我们应如何防范上述的私隐问题?</p>
<p>需要记住的是，任何涉及操作环境保护策略的代码都是自动化测试的理想选择。</p>
<p>在 <code>icdab_sample</code> 项目中我们已经创建了单元测试和 UI 测试。</p>
<p>当将测试用例应用于处理琐碎的程序时，我们总会感到无所适从。 但是考虑一个具有拓展性的 <code>Info.plist</code> 文件的大型程序。当我们要创建一个新版本而需要创建另一个新的 <code>Info.plist</code> 文件。那么在不同构建目标建如何保持隐私协议设置的同步就会成为一个问题。那么仅针对启动相机的 UI 测试代码可以轻松捕获到此类问题，因此具有实用的商业价值。</p>
<p>同样，如果我们的 APP 包含大量的底层代码，然后我们需要将APP从iOS系统移植到tvOS，那么有多少涉及操作系统的敏感代码会仍然适用呢。</p>
<p>针对不同的设计考虑全面地对顶级功能进行单元测试可以抵消在我们对代码库中深入研究和单元测试潜在的辅助函数调用所耗费的精力。 这是一项战略性工作，可让我们在移植到 Apple 生态系统（及以后）中的其他平台时对自己的应用程序和对问题领域的早期反馈充满信心。</p>
<h3 id="单元测试获取-mac-地址">单元测试获取 MAC 地址</h3>
<p>获取MAC地址的代码并非易事。 因此，它需要进行一定程度的测试。</p>
<p>下面是单元测试代码的摘录：</p>
<pre><code>    func getFirstOctectAsInt(_ macAddress: String) -&gt; Int {
        let firstOctect = macAddress.split(separator: &quot;:&quot;).first!
        let firstOctectAsNumber = Int(String(firstOctect))!
        return firstOctectAsNumber
    }

    func testMacAddressNotNil() {
        let macAddress = MacAddress().getMacAddress()
        XCTAssertNotNil(macAddress)
    }

    func testMacAddressIsNotRandom() {
        let macAddressFirst = MacAddress().getMacAddress()
        let macAddressSecond = MacAddress().getMacAddress()
        XCTAssert(macAddressFirst == macAddressSecond)
    }

    func testMacAddressIsUnicast() {
        let macAddress = MacAddress().getMacAddress()!
        let firstOctect = getFirstOctectAsInt(macAddress)
        XCTAssert(0 == (firstOctect &amp; 1))
    }

    func testMacAddressIsGloballyUnique() {
        let macAddress = MacAddress().getMacAddress()!
        let firstOctect = getFirstOctectAsInt(macAddress)
        XCTAssert(0 == (firstOctect &amp; 2))
    }</code></pre>
<p>实际上，最后一个测试用例会失败，因为操作系统会返回本地地址。</p>
<h3 id="ui-tests-测试访问相机">UI Tests 测试访问相机</h3>
<p>为了测试访问相机的功能，我们编写了一个简单的 UI 测试用例，模拟按下了拍照按钮（通过辅助功能标识符 <code>takePhotoButton</code> ）</p>
<pre><code>func testTakePhoto() {
    let app = XCUIApplication()
    app.buttons[&quot;takePhotoButton&quot;].tap()
}</code></pre>
<p>这个 UI 测试代码会导致立即崩溃。</p>
<blockquote>
<p>如果未授权的话</p>
</blockquote>
<h1 id="工具">工具</h1>
<h2 id="概览">概览</h2>
<p>我们有一套丰富的工具可用于协助 <code>crash dump</code> 的分析。如果使用得当，这可以节省大量时间。</p>
<p>Xcode 提供了很多现成的帮助。然而，如何使用和理解 Xcode 工具提供的信息是令人望而生畏的。在后面的章节中，我们将介绍这些工具的使用示例。</p>
<p>此外，在 macOS 中还提供了标准的命令行工具。这些工具在特定场景下有这难以想象的作用，尤其在我们已经知道我们需要找到什么的时候。我们将介绍具体的场景，并说明该如何使用这些工具。</p>
<p>接下来是帮助我们逆向工程的软件工具。有时候在使用第三方库时，有些问题很难被我们发现。那么除了查看文档或提出支持请求外，还可以使用这些工具自行进行调查。</p>
<h2 id="逆向工程">逆向工程</h2>
<p>逆向工程是研究已构建的二进制文件（例如应用程序，库或辅助进程守护程序），以确定它是如何工作的。对于一个特定的对象，我们可能想知道：</p>
<ul>
<li>它所提供对象的生命周期是什么？</li>
<li>它对对象做什么检查？</li>
<li>它依赖哪些文件或资源？</li>
<li>它为什么返回故障代码？</li>
</ul>
<p>通常，我们不用了解所有信息，只需要某些特定的东西来帮助建立假设。 一旦我们得到一个假设，我们将测试它与我们正在处理的 <code>crash dump</code> 是否有关。</p>
<p>逆向工程应该走多远，应该投入多少时间和金钱？我们提供以下建议：</p>
<ul>
<li>如果我们刚刚开始我们的应用程序开发人员之旅，或者我们的资金有限，那么只需坚持使用标准的 Xcode 工具，macOS 命令行和 <code>class-dump</code> 工具。</li>
<li>如果我们是专业的应用程序开发人员，强烈建议购买商业逆向工程工具。最引人注目的是 <code>Hopper</code> ; 它提供了许多由 IDA Pro（一种高端工具）提供的功能。价格合理，即使只使用了几次，也可以提高生产力。我们在本书中将展示如何使用 Hopper。</li>
<li>如果我们是一个专业的渗透测试人员、逆向工程师或安全研究员，那么我们可能希望投资于顶级软件工具 IDA Pro。该工具成本高达数千美元，但通常是在公司范围内购买的。</li>
</ul>
<h2 id="class-dump-工具">Class Dump 工具</h2>
<p>Objective-C 运行时的一大优点是它在其构建的二进制文件中携带了大量丰富的程序结构信息。这些在语言的动态上得以发挥作用。实际上，灵活的动态调度是许多崩溃的源头。我们建议安装 <code>class-dump</code> 因为我们将在后面的章节中引用它的用法。</p>
<blockquote>
<p>点击 <a href="%5Bhttp://stevenygard.com/projects/class-dump/%5D(http://stevenygard.com/projects/class-dump/)">Class-dump</a> 下载安装。</p>
</blockquote>
<p><code>class-dump</code> 工具允许我们查看给定程序中的Objective C 类、方法和属性。</p>
<h2 id="第三方工具手机崩溃报告">第三方工具手机崩溃报告</h2>
<p>虽然 iTunes Connect 中的 Apple Crash Reporter 工具非常的出色，但是仍有改进空间。</p>
<p>一个强大的开源软件，由 Plausible Labs 的 Landon Fuller 所编写 <code>plcrashreporter</code>。<span class="citation" data-cites="plcrashreporter">(“Plausible Labs Crash Reporter” 2018)</span></p>
<p>我们想让让我们的应用程序能够尽可能的处理发生的所有可能的信号和异常，一旦应用程序将无法实现，从而导致底层操作系统触发崩溃。</p>
<p>通过这个工具，可以记录奔溃信息，然后传输到我们自己的服务器上。</p>
<p>这有两个好处。首先，崩溃处理程序可以处理 Apple <code>ReportCrash</code> 工具尚未处理的边缘情况。其次，可以采用更全面的服务器端解决方案。</p>
<p>对于那些想要探索和理解操作系统以及底层应用程序代码的人来说，<code>plcrashreporter</code> 提供了一个学习精心设计的系统软件的绝好机会。</p>
<p>当公司拥有许多应用程序，许多应用程序变体，并且拥有基于 Android 等其他平台的应用程序时，就需要更强大的多平台解决方案。处理崩溃报告很快就会成为一种管理问题。哪个崩溃最严重？ 有多少客户受到影响？ 质量和稳定性的指标是什么？</p>
<p>有许多可用的商业解决方案，都主要基于上述开源项目。</p>
<p>移动软件开发领域在过去几年已经发展成为一个大产业。。许多专业公司为 App 开发者提供专业的服务。 该领域在合并和收购方面非常活跃。由于这个行业的变化之快，我们没办法在本书中列举这些专业公司的名称。</p>
<p>阅读 <code>rollout.io</code> 的博客是一个好的开始，它介绍了市场上的竞争者们。<span class="citation" data-cites="3rdpartycrashtools">(“IOS Crash Reporting Tools” 2017)</span></p>
<h1 id="xcode-内置工具">Xcode 内置工具</h1>
<p>Xcode为开发人员在理解和预防崩溃方面提供了重要的帮助。</p>
<p>Xcode 给我们提供了两个层面的帮助，对于简单的现象，Xcode 会直接告诉我们常见的错误并建议我们更正，而对于复杂情况，Xcode 会为我们提供原始的信息，但是这需要我们利用操作系统的相关知识来解释这些信息。</p>
<p>后续文章中我们将会多次提及 Xcode 的配置、设置和工具。但是尽管如此，让我们首先看看Xcode 提供的简单但有效的帮助。</p>
<h2 id="xcode-诊断设置">Xcode 诊断设置</h2>
<p>通过打开项目 <code>icdab_sample</code>，并查看 Scheme （选择 Edit Scheme），然后选中 Diagnostics 选项，我们看到以下内容：</p>
<p><img src="screenshots/diagnostic_settings.png" /></p>
<h3 id="执行方法">执行方法</h3>
<p>如果我们的崩溃可以从我们自己的开发环境或源码中得到复现，那么接下来的操作是我们应该打开适当的诊断设置项，然后重新运行我们的应用程序。</p>
<p>随着我们对各种诊断项的熟悉，我们会知道要选择哪个选项。我们将研究不同的场景，以便我们了解如何使用每个诊断设置项。但是，当我们开始了解诊断设置项的价值时，我们需要一步一步的了解一下可选项。基本方法是：</p>
<ol type="1">
<li>对遇到的问题编写单元测试用例或 UI 测试用例。</li>
<li>我们猜测分析并仅选择上述诊断项中的一个。</li>
<li>执行我们的测试用例</li>
<li>观察Xcode中的任意警告或打印信息。</li>
<li>如果并没有解释问题，重复上述操作，但是选择另外的诊断项。</li>
</ol>
<h3 id="分析方法">分析方法</h3>
<p>另一种用于分析和避免崩溃的方法是运行代码分析器。使用<code>Command-Shift-B</code> 调用。</p>
<p>下面是 <code>icdab_sample</code> 的分析报告:</p>
<pre><code>/Users/faisalm/dev/icdab/source/icdab_sample/icdab_sample/
macAddress.m:22:12:
 warning: Null pointer argument in call to string length function
    assert(strlen(nullPointer) == 0);</code></pre>
<p>很方便的给我们标记了源码。</p>
<p><img src="screenshots/analyser_null.png" /></p>
<p>我们可以在项目构建时判断是否启用该功能，对于 <code>shallow</code> 或 <code>deep</code> 模式的选择，可以根据我们的认知权衡是应该使用较慢但是更为彻底的分析还是较快但是简单的分析。它在 Xcode 项目文件的 Build Settings 选项中。</p>
<p><img src="screenshots/static_analyser_build.png" /></p>
<p>对于从未生成代码分析报告的大型项目来说，其输出的数据可能是海量的。报告中会有一些问题，但是总体来说做的很好。报告中会有重复，因为某些类型的错误会在整个代码中重复出现。</p>
<p>如果我们使用敏捷开发的方法来开发迭代软件，则可以将代码分析列为待办事项，可以在分配给重构和维护的时间内进行处理。</p>
<p>在大型软件项目中，重构和维护应该占整体工作的 20% 左右。这一部分出现了很多不同观点。笔者认为可以与正常开发同时进行，只要正在进行的工作没有出现高风险的变化即可。对于有高风险的变化，可以留到应用程序完成重大更新之后进行代码扥西。通常，在应用发布之后会有一段时间进行下个版本的规划，这段时间允许开发者去解决此类问题。</p>
<h4 id="ios-quickedit-app-案例研究">iOS QuickEdit App 案例研究</h4>
<p>从经济学的角度来看，利用代码分析发现潜在崩溃，不失为一个解决问题的很好的投资。例如，在 Quickedit iOS 应用程序中，大约有100万行 Objective-C 代码，每天有 7 万活跃用户，当运行代分析后发现 13 个明显的崩溃问题。 我们创建了一个开发任务（“修复明显的代码分析错误”）。所有的 13 个问题在一天之内得到了修复然后测试在花费了两天进行测试。 对用户来说，崩溃现象是难以接受的。在生产环境发现的问题通常比开发环境要多付出 20 倍的努力和成本。由于用户群里的数量，可能让崩溃的影响更为严重，这 13 问题可能需要我们浪费 <code>20 * 3 = 60</code> 天的工作量。</p>
<p>由于年代的原因，QuickEdit 应用使用手动引用计数的 Objective-C 代码。但是尽管如此，基于应用的代码分析，它的可靠性为 99.5%。一旦这些问题得到解决，大概只需要花费 5% 的工作量就可以保持工程的稳定性。</p>
<h3 id="方法论">方法论</h3>
<p>一个有效的从我们应用程序中减少崩溃的方法，尤其是当我们在大型组织中时，是将代码分析纳入我们的软件开发过程中。</p>
<p>当开发人员拉取服务器代码时，请确保开发者不会引入新的分析警告。我们可以把代码分析报告当做被免费提供的自动进行的 <code>code review</code>。尤其对独立开发者来说，没有其他人进行 <code>code review</code>，特别有用。</p>
<p>将代码提交到功能分支时，请在其上运行自动化测试，并设置不同的诊断项配置。这可以自动解决问题。</p>
<p>每次发布之前，分配一些时间使用 <code>memory profiler</code> 去运行一些特定的测试用例，来查看内存使用情况或其他关键指标。记录内存峰值以及配置文件。然后，当以后发布版本时，我们就有了一个标准，从数量和质量两个维度来分析新版本。</p>
<h2 id="行百里者半九十">行百里者半九十</h2>
<p>大多数软件开发人员都知道他们 “应该” 做什么；整洁的代码、适当的测试、代码评审等。</p>
<p>我们建议采取针对性的方法。花费时间去一起研究示例程序来理解概念。去写一个原型代码只需要证明一个业务用例即可。去编写更多人使用并高度信赖的代码。</p>
<p>我们认为应该尽可能的去思考经济成本，因为大多数开发者都参与了专业的软件开发。或者，当我们从事非商业项目或业余项目时，那么经济成本就是我们的业余时间，我们更加希望能够有效的利用我们的时间。</p>
<p>我们建议：</p>
<ul>
<li>对于示例程序和概念的研究，直接去编写代码。</li>
<li>对于原型开发来说，当我们遇到问题时去执行方法就行。</li>
<li>对于独立开发的产品，从一开始就进行代码分析，并在发现重大问题时添加到我们的开发流程中。在开始开发时可以带着编写用例，但是可以在运到重大问题时才去执行。</li>
<li>对于团队开发的产品，请添加到开发流程中。从开始开发时就进行全面的测试。</li>
</ul>
<h1 id="混合环境">混合环境</h1>
<p>我们已经了解到 Xcode 提供了许多用于崩溃转储分析和预防崩溃的自动工具。然而这些并不能为我们提供需要的所有答案。我们需要有一个面向设计的观点来思考问题。</p>
<p>在本章中，我们将去研究一个使用混合语言和编程范式的示例应用程序 <code>icdab_planets</code>。它告诉我们为什么还必须考虑设计问题。</p>
<h2 id="项目结构">项目结构</h2>
<p>示例应用程序 <code>icdab_planets</code> 采用 C++ 和 Objective-C++ 的混合编程。它同时依赖于 STL 数据结构和传统的 Objective-C 数据结构。</p>
<p>应用程序的 <code>model</code> 层是用 C++ 编写的。应用程序的 <code>controller</code>层是用 Objective-C++ 编写的。</p>
<p>该应用程序的目的是告诉我们木星内部可以容纳多少个冥王星大小的行星。</p>
<h2 id="范式">范式</h2>
<p>回想一下，我们证明了：</p>
<ul>
<li>Objective-C 允许向 nil 对象发送消息</li>
<li>C 语言环境中，引用NULL 时会发生崩溃</li>
</ul>
<p>这里，我们展示了 C++ 标准模板库具有怎样 <code>back-fill</code> 策略。</p>
<p>在 STL 映射抽象（哈希表）中，当我们查询一个不存在的条目时，STL 将在表中插入一个新的条目，用于查询所查询的关键字，然后返回该条目，而不是返回一个错误或返回一个 nil。</p>
<h2 id="问题">问题</h2>
<p>在示例应用程序（该应用程序在启动时崩溃）中，有一个断言被触发。</p>
<pre><code>double pluto_volume = pluto.get_volume();
assert(pluto_volume != 0.0);

double plutos_to_fill_jupiter
        =  jupiter.get_volume() / pluto_volume;</code></pre>
<p>启动代码分析并不会发现任何问题或警告。</p>
<p>代码中的断言是为了避免被零除。 事实上断言被触发其实很好，因为我们知道从哪里开始调试问题。</p>
<p>因为这段代码，冥王星的质量为 0.0。</p>
<pre><code>planet pluto = planet::get_planet_with_name(&quot;Pluto&quot;);</code></pre>
<p>返回直径为零的行星。</p>
<p>在代码文件 <code>planet_data.hpp</code>中我们可以看到这个 API：</p>
<pre><code>static planet get_planet_with_name(string name);</code></pre>
<p>因此，无论我们传递任何名称，我们都能得到一个<code>planet</code>作为回应；这个值永远不为 <code>NULL</code>。</p>
<p>问题是对于该 API 需要更为严谨的思考。它只是为了完成工作而做的一个简单的抽象。</p>
<p>这里有</p>
<pre><code>planet planet::get_planet_with_name(string name) {
    if (!database.loaded_data) {
        database.load_data();
    }
    return database.planets[name];
}</code></pre>
<p>乍一看，可能是由于数据库没办法正确加载数据。 事实上是因为数据中缺少冥王星对应的条目：</p>
<pre><code>void planet_database::load_data() {
    planet planet_Mercury =
     planet(&quot;Mercury&quot;, 4878.0, 57.9 * millionKm);
    planets[&quot;Mercury&quot;] = planet_Mercury;

    planet planet_Venus =
     planet(&quot;Venus&quot;, 12104, 108.2 * millionKm);
    planets[&quot;Venus&quot;] = planet_Venus;

    planet planet_Earth =
     planet(&quot;Earth&quot;, 12756, 149.6 * millionKm);
    planets[&quot;Earth&quot;] = planet_Earth;

    planet planet_Mars =
     planet(&quot;Mars&quot;, 6792, 227.9 * millionKm);
    planets[&quot;Mars&quot;] = planet_Mars;

    planet planet_Jupiter =
     planet(&quot;Jupiter&quot;, 142984, 778 * millionKm);
    planets[&quot;Jupiter&quot;] = planet_Jupiter;

    planet planet_Saturn =
     planet(&quot;Saturn&quot;, 120536, 1427 * millionKm);
    planets[&quot;Saturn&quot;] = planet_Saturn;

    planet planet_Uranus =
     planet(&quot;Uranus&quot;, 51118, 2870 * millionKm);
    planets[&quot;Uranus&quot;] = planet_Uranus;

    planet planet_Neptune =
     planet(&quot;Neptune&quot;, 49532, 4497 * millionKm);
    planets[&quot;Neptune&quot;] = planet_Neptune;

//  No longer considered a planet but instead a dwarf planet
//  planet planet_Pluto =
//   planet(&quot;Pluto&quot;, 2370, 7375 * millionKm);
//  planets[&quot;Pluto&quot;] = planet_Pluto;

    loaded_data = true;
}</code></pre>
<p>这个问题的间接表现是 <code>database.planets[name]</code> 并没有获取到冥王星的数据，因此通过<code>no-arg</code>构造函数创建了一个条目，这是 STL 映射数据结构的行为。</p>
<pre><code>planet::planet() {
    this-&gt;name = &quot;&quot;;
    this-&gt;diameter = 0.0;
    this-&gt;distance_from_sun = 0.0;
}</code></pre>
<p>在这种情况下，我们看到默认构造函数中将直径为零。</p>
<h2 id="解决方案">解决方案</h2>
<p>我们发现问题是因为没有合适的运用各种语言的框架和范式，当我们混用这些范式时，每个抽象层都会掩盖不同的假设。</p>
<p>在 STL 中，我们期望完成<code>find</code> 操作，而不是索引操作符。这就允许抽象标记可以找不到的对应的条目。</p>
<p>在 Objective-C 中，我们期望 lookup API 是一个返回给定查找名称的索引的函数。另外，当操作失败时，索引将被置为 <code>NSNotFound</code>。</p>
<p>在此代码示例中，每个抽象层都假设另一侧将边缘案例重新映射为适当的形式。</p>
<h3 id="stl-解决方案">STL 解决方案</h3>
<p>从 STL 的角度来看，我们有一个可以<code>正确地</code>执行操作代码变体。在文件<code>example/planets_stl</code>中。 在方法获取时，我们可以有一个辅助的方法：</p>
<pre><code>- (BOOL)loadPlanetData {
    auto pluto_by_find = planet::find_planet_named(&quot;Pluto&quot;);
    auto jupiter_by_find = planet::find_planet_named(&quot;Jupiter&quot;);

    if (planet::isEnd(jupiter_by_find) ||
     planet::isEnd(pluto_by_find)) {
        return NO;
    }
    pluto = pluto_by_find-&gt;second;
    jupiter = jupiter_by_find-&gt;second;
    return YES;
}</code></pre>
<p>对于一个主要使用 Objective-C 语言的开发者来说这段代码很难理解。但是如果该项目主要以 C++ 开发的话，具有特定的相关平台经验，那么这也许是可以接受的。如果代码库只使用了部分的 C++ 代码，那么更好的解决方案是将特定范式限制在文件中，并应用外观设计模式，在特定的平台代码端提供遵循 Objective-C 范式的 API 版本。</p>
<p>然后，ViewController 代码中可以不使用 Objective-C++，而是将其作为 Objective-C 文件。</p>
<h3 id="外观模式解决方案">外观模式解决方案</h3>
<blockquote>
<p>外观模式是一种设计模式，我认为是类似于工厂模式的，请查看<a href="https://zh.wikipedia.org/wiki/%E5%A4%96%E8%A7%80%E6%A8%A1%E5%BC%8F">维基百科</a></p>
</blockquote>
<p>这里有一个外观模式解决方案 <code>example/facade_planets</code> 用来解决混合编程的问题</p>
<p>外观模式解决方案:</p>
<pre><code>@implementation PlanetModel

- (id)init {
    self = [super init];

    NSString *testSupportAddPluto =
     [[[NSProcessInfo processInfo] environment]
      objectForKey:@&quot;AddPluto&quot;];

    if ([testSupportAddPluto isEqualToString:@&quot;YES&quot;]) {
        planet::add_planet(
          planet(&quot;Pluto&quot;, 2370, 7375 * millionKm));
    }

    if (self) {
        _planetDict = [[NSMutableDictionary alloc] init];
        auto pluto_by_find =
         planet::find_planet_named(&quot;Pluto&quot;);
        auto jupiter_by_find =
         planet::find_planet_named(&quot;Jupiter&quot;);

        if (planet::isEnd(jupiter_by_find) ||
         planet::isEnd(pluto_by_find)) {
            return nil;
        }
        auto pluto = pluto_by_find-&gt;second;
        auto jupiter = jupiter_by_find-&gt;second;

        PlanetInfo *plutoPlanet = [[PlanetInfo alloc] init];
        plutoPlanet.diameter = pluto.get_diameter();
        plutoPlanet.distanceFromSun =
         pluto.get_distance_from_sun();
        plutoPlanet.volume = pluto.get_volume();
        assert (plutoPlanet.volume != 0.0);
        [_planetDict setObject:plutoPlanet forKey:@&quot;Pluto&quot;];

        PlanetInfo *jupiterPlanet = [[PlanetInfo alloc] init];
        jupiterPlanet.diameter = jupiter.get_diameter();
        jupiterPlanet.distanceFromSun =
         jupiter.get_distance_from_sun();
        jupiterPlanet.volume = jupiter.get_volume();
        assert (jupiterPlanet.volume != 0.0);
        [_planetDict setObject:jupiterPlanet forKey:@&quot;Jupiter&quot;];
    }

    return self;
}

@end</code></pre>
<p>然后，API 调用方变成纯粹的 Objective-C 类：</p>
<pre><code>- (void)viewDidLoad {
    [super viewDidLoad];

    self.planetModel = [[PlanetModel alloc] init];

    if (self.planetModel == nil) {
        return;
    }

    double pluto_diameter =
     self.planetModel.planetDict[@&quot;Pluto&quot;].diameter;
    double jupiter_diameter =
     self.planetModel.planetDict[@&quot;Jupiter&quot;].diameter;
    double plutoVolume =
     self.planetModel.planetDict[@&quot;Pluto&quot;].volume;
    double jupiterVolume =
     self.planetModel.planetDict[@&quot;Jupiter&quot;].volume;
    double plutosInJupiter = jupiterVolume/plutoVolume;

    self.plutosInJupiterLabelOutlet.text =
    [NSString stringWithFormat:
    @&quot;Number of Plutos that fit inside Jupiter = %f&quot;,
     plutosInJupiter];

    self.jupiterLabelOutlet.text =
    [NSString stringWithFormat:
     @&quot;Diameter of Jupiter (km) = %f&quot;,
     jupiter_diameter];
    self.plutoLabelOutlet.text =
    [NSString stringWithFormat:
     @&quot;Diameter of Pluto (km) = %f&quot;,
     pluto_diameter];
}</code></pre>
<h2 id="经验教训">经验教训</h2>
<p>这里的经验是崩溃可能来自特殊情况处理。由于不同的语言和框架以它们自己惯用的方式处理特殊的情况，因此如果可能的话，将代码分离出来并使用一个特定的外观来保持各种范式清晰分离是比较安全的。</p>
<h1 id="符号化">符号化</h1>
<p>本章介绍了 crash dump 的符号化。符号化是将机器地址映射成对拥有源代码的程序员有意义的符号地址的过程。我们希望尽可能看到函数名（加上任何偏移量），而不是看到机器地址。</p>
<p>我们使用 <code>icdab_planets</code> 示例应用程序来演示崩溃。<span class="citation" data-cites="icdabgithub">(“IOS Crash Dump Analysis Book Github Resources” 2018)</span></p>
<p>当处理真实的崩溃时，一般会涉及到很多不同的关联数据。这些数据可以来自用户终端，通过设置允许将崩溃报告上传到 Apple 的终端设备，Apple 拥有的符号信息和我们本地开发环境的配置信息可以相互映射。</p>
<p>为了理解所有信息是如何组合在一起的，最好从一开始开始就自己完成数据转化任务，因此一旦我们需要诊断符号化问题，我们就已有一定的技术经验。</p>
<h2 id="构建过程">构建过程</h2>
<p>通常，当我们开发应用程序时，我们会将应用程序的 <code>Debug</code> 版本构建到我们的设备上。而当我们为测试人员、应用程序审核或应用商店构建应用时，我们构建应用程序的 <code>Release</code> 版本。</p>
<p>默认情况下，对于 <code>Release</code>版本，<code>.o</code> 文件的调试信息被存储在一个单独的目录结构中。被称作 <code>our_app_name.DSYM</code>。</p>
<p>当开发人员发现崩溃时，可以使用这些调试信息来帮助我们理解程序在哪里出错了。</p>
<p>当用户发现我们的应用程序崩溃时，并没有开发人员在身边。所以，会生成一份崩溃报告。它包含出现问题的机器地址。符号化可以将这些地址转换为有意义的源代码来作为参考。</p>
<p>为了进行符号化，必须拥有对应的 DSYM 文件。</p>
<p>默认情况下，Xcode 被设置为只为 <code>Release</code> 版本生成 DSYM 文件，<code>Debug</code> 版本则不会生成该文件。</p>
<h2 id="构建设置">构建设置</h2>
<p>打开 Xcode，选择 build settings，搜索 “Debug Information Format”，可以看到如下设置：</p>
<table>
<thead>
<tr class="header">
<th>Setting</th>
<th>Meaning</th>
<th>Usually set for target</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>DWARF</td>
<td>调试信息仅在 <code>.o</code> 文件中</td>
<td>Debug</td>
</tr>
<tr class="even">
<td>DWARF with dSYM File</td>
<td>除了<code>.o</code> 文件，也会将调试信息整理到 DSYM 文件中</td>
<td>Release</td>
</tr>
</tbody>
</table>
<p>在默认设置中，如果我们在测试设备上调试 APP 时，点击应用图标并启动 APP ，那么如果发生崩溃，我们并没有在崩溃报告中看到任何符号。这使许多人感到困惑。</p>
<p>这是因为二进制文件的 UUID 和 DSYM 并不匹配。</p>
<p>为了避免这个问题，示例程序 <code>icdab_planets</code> 在 <code>Debug</code>和 <code>Release</code> 两个 Target 中全都设置为 <code>DWARF with dSYM File</code> 。然后我们就可以进行符号化，因为在 Mac 上会生成一个可供匹配的 DSYM。</p>
<h2 id="关注开发环境的崩溃">关注开发环境的崩溃</h2>
<p><code>icdab_planets</code> 程序被设计为在启动时由于断言而崩溃。</p>
<p>如果没有设置成<code>DWARF with dSYM File</code>，我们会得到一个象征性的部分符号化的崩溃报告。</p>
<p>从 <code>Windows-&gt;Devices and Simulators-&gt;View Device Logs</code> 中看到的崩溃报告看起来像这样（为了便于演示而截断）</p>
<pre><code>Thread 0 Crashed:
0   libsystem_kernel.dylib        
0x0000000183a012ec __pthread_kill + 8
1   libsystem_pthread.dylib       
0x0000000183ba2288 pthread_kill$VARIANT$mp + 376
2   libsystem_c.dylib               
0x000000018396fd0c abort + 140
3   libsystem_c.dylib               
0x0000000183944000 basename_r + 0
4   icdab_planets                   
0x00000001008e45bc 0x1008e0000 + 17852
5   UIKit                           
0x000000018db56ee0
-[UIViewController loadViewIfRequired] + 1020

Binary Images:
0x1008e0000 - 0x1008ebfff icdab_planets arm64
  &lt;9ff56cfacd66354ea85ff5973137f011&gt;
   /var/containers/Bundle/Application/
   BEF249D9-1520-40F7-93F4-8B99D913A4AC/
   icdab_planets.app/icdab_planets</code></pre>
<p>但是，如果设置成<code>DWARF with dSYM File</code>，崩溃报告则会像这样：</p>
<pre><code>Thread 0 Crashed:
0   libsystem_kernel.dylib          
0x0000000183a012ec __pthread_kill + 8
1   libsystem_pthread.dylib         
0x0000000183ba2288
pthread_kill$VARIANT$mp + 376
2   libsystem_c.dylib               
0x000000018396fd0c abort + 140
3   libsystem_c.dylib               
0x0000000183944000 basename_r + 0
4   icdab_planets                   
0x0000000104e145bc
-[PlanetViewController viewDidLoad] + 17852
 (PlanetViewController.mm:33)
5   UIKit                           
0x000000018db56ee0
-[UIViewController loadViewIfRequired] + 1020</code></pre>
<p>报告的第0、1、2、5行在两种情况下是相同的，因为我们的开发环境具有正在测试的 iOS 版本的符号信息。在第二种情况下，Xcode 将查找 DSYM 文件以阐明第 4 行。它告诉我们这是在 PlanetViewController.mm 文件中的第33行。 是：</p>
<pre><code>assert(pluto_volume != 0.0);</code></pre>
<h2 id="dsym-结构">DSYM 结构</h2>
<p>DSYM 文件严格来说是一个目录层次结构：</p>
<pre><code>icdab_planets.app.dSYM
icdab_planets.app.dSYM/Contents
icdab_planets.app.dSYM/Contents/Resources
icdab_planets.app.dSYM/Contents/Resources/DWARF
icdab_planets.app.dSYM/Contents/Resources/DWARF/icdab_planets
icdab_planets.app.dSYM/Contents/Info.plist</code></pre>
<p>只是将通常放在 <code>.o</code> 文件中的 DWARF 数据，复制到另一个单独的文件中。</p>
<p>通过查看构建日志，我们可以看到 DSYM 是如何生成的。它实际上只是因为这个命令： <code>dsymutil path_to_app_binary -o output_symbols_dir.dSYM</code></p>
<h2 id="着手符号化">着手符号化</h2>
<p>为了帮助我们熟悉 crash dump 报告，我们可以演示实际上符号化是如何工作的。在第一段报告中，我们想要了解：</p>
<pre><code>4   icdab_planets                   
0x00000001008e45bc 0x1008e0000 + 17852</code></pre>
<p>如果我们能在崩溃时准确的知道代码的版本，我们就可以重新编译我们的程序，但是在 DSYM 设置打开的情况下，我们只能在在发生崩溃后获取一个 DSYM 文件。</p>
<p>crash dump 告诉我们崩溃发生时程序在内存中的程序在内存中的地址信息。这告诉我们其他地址（TEXT）位置相对偏移量。</p>
<p>在crash dump 报告的底部，我们有一行<code>0x1008e0000 - 0x1008ebfff icdab_planets</code>。 所以 icdab_planets 的位置从 <code>0x1008e0000</code> 开始。</p>
<p>运行命令 <code>atos</code> 查看你感兴趣的位置信息：</p>
<pre><code># atos -arch arm64 -o
 ./icdab_planets.app.dSYM/Contents/Resources/DWARF/
icdab_planets -l 0x1008e0000 0x00000001008e45bc
-[PlanetViewController viewDidLoad] (in icdab_planets)
 (PlanetViewController.mm:33)</code></pre>
<p>崩溃报告工具基本上就是使用 <code>atos</code>命令来符号化崩溃报告，以及提供其他与系统相关的信息。</p>
<p>如果想要更加深入的了解符号化过程我们可以通过 Apple Technote 来获取其进一步的描述。<span class="citation" data-cites="tn2123">(“CrashReport Technote 2123” 2004)</span></p>
<h2 id="逆向工程方法">逆向工程方法</h2>
<p>在上面的例子中，我们具有crash dump 的源代码和符号，因此可以执行符号化。</p>
<p>但是有时在我们的项目中，包含了第三方的二进制框架，我们并没有源代码。如果框架提供者提供了相应的符号信息让用户可以进行 crash dump 分析，这当然是很好的。但是当符号信息不可用时，我们仍然可以通过一些逆向工程的手段来取得进展。</p>
<p>与第三方合作时，故障的诊断和排查通常需要更多的时间。我们发现良好的编写且具体的错误报告可以加速很多事情。以下方法可以为你提供所需的特定信息。</p>
<p>我们将使用工具一章中提到的 Hopper 工具来演示我们的方法。</p>
<p>启动 hopper，选择 <em>File-&gt;Read Executable to Disassemble</em>。我们使用 <code>examples/assert_crash_ios/icdab_planets</code>中的二进制文件作为示例。</p>
<p>我们需要 “rebase” 反汇编程序，以便它在崩溃时显示的地址与程序的地址相同。选择 <code>Modify-&gt;Change File Base Address</code>。为了保持一致，输入 <code>0x1008E0000</code>。</p>
<p>现在我们可以看到崩溃代码了。地址 <code>0x00000001008e45bc</code> 实际上是设备在跟踪堆栈中执行函数调用后将 <em>return</em> 到的地址。尽管如此，它仍被记录在此。选择 <code>Navigate-&gt; Go To Address and Symbol</code> 并输入 <code>0x00000001008e45bc</code> 。</p>
<p>我们看到的总体会如下所示</p>
<p><img src="screenshots/hopperAddressView.png" /></p>
<p>放大这一行，我们能看到</p>
<p><img src="screenshots/hopperPlanetAbort.png" /></p>
<p>这确实显示了 assert 方法的返回地址。再往上看，我们看到判断了 Pluto 的体积不能为零。这只是 Hopper 非常基本的使用示例。接下来我们将使用 Hopper 演示其最有趣的功能——将汇编代码生成伪代码。这降低了理解崩溃的心理负担。如今，大多数开发人员很少查看汇编代码，所以就这个功能就值得我们为该软件付出代价。</p>
<p>现在至少对于当前的问题吗，我们可以编写一个错误报告，指明由于 Pluto 的体积为零，导致代码崩溃了。对框架的提供者来说，这就足以解决问题了。</p>
<p>在更复杂的情况下，想象我们使用了一个发生崩溃的图片转换库。图片可能有多种像素格式。使用 <code>assert</code> 可以让我们注意到某些特定的像素格式。因此，我们可以尝试其他的像素格式。</p>
<p>另一个例子是 security 库。安全代码通常会返回通用错误代码，而不是特定的故障代码，以便将来进行代码增强并避免泄漏内部细节（安全风险）。安全库中的 crash dump 程序可能会指出安全问题的类型，并帮助我们更早地更正传递到库中的某些数据结构。</p>
<h1 id="崩溃报告">崩溃报告</h1>
<p>在本章中，我们将详细介绍崩溃报告中的内容。</p>
<p>我们将主要关注 iOS 奔溃报告。我们还将介绍 macOS 的奔溃报告，虽然报告的结构略有不同，但都足以让我们获取信息。</p>
<p>虽然，目前部分 App 可以会通过安装使用第三方的奔溃处理程序，以增加获取崩溃报告和诊断的能力，或者是基于 Web 服务来管理大量用户设备的奔溃信息。但是在本章中，我们假设 App 没有安装这种三方库，因此我们使用 <code>Apple CrashReport</code> 工具来处理奔溃报告。</p>
<p>当 App 发生奔溃时，<code>ReportCrash</code>从操作系统的崩溃过程中提取相关信息，并生成拓展名为<code>.crash</code>的文本文件。</p>
<p>当符号信息可用时，Xcode 将符号化奔溃报告然后显示符号化以后的名称而不是机器地址。这就提高了报告的可阅读性，更容易理解。</p>
<p>App 已经制作了一份详细的文档来解释 <code>crash dump</code> 的全部结构。</p>
<h2 id="系统诊断">系统诊断</h2>
<p>崩溃报告只是更大的系统诊断报告中的一部分。</p>
<p>通常，作为 App 的开发人员我们并不需要有进一步的更多的了解。但是，如果我们的问题可能是由一系列无解释的事件，或者是与硬件或与 Apple 提供的系统服务之间更复杂的交互而引起的，这时候我们不仅仅需要查看奔溃报告，而需要研究系统诊断报告。</p>
<h3 id="提取系统诊断信息">提取系统诊断信息</h3>
<p>当需要了解导致崩溃发生的环境时，我们可能需要安装手机设备管理配置文件（用于打开调试某些子系统），或创建虚拟网络接口（用于网络监测）。 Apple 提供了一个涵盖每个场景的 <a href="https://developer.apple.com/bug-reporting/profiles-and-logs/">网页</a>。</p>
<p>在 iOS 上，基本思路是我们先安装一个配置文件，这个配置文件会让设备记录更多的日志，然后复现崩溃操作（或者让客户这么操作）。 然后我们按下设备上的特殊按键组合（例如，同时按下音量按钮和侧按钮）。系统会短暂振动，表明它正在运行<code>sysdiagnose</code> 程序，这个程序会提取很多日志文件。然后我们用 iTunes 同步设备以检索生成的<code>sysdiagnose_date_name.tar.gz</code> 文件。打包文件中包含许多系统和子系统日志，我们可以看到崩溃发生的时间以及引起崩溃的上下文。</p>
<p>在 macOS 上我们也可以执行相同的操作。</p>
<h2 id="ios-崩溃报告导览">iOS 崩溃报告导览</h2>
<p>在这里，我们将浏览 iOS 崩溃报告中的每个部分并解释相应的字段。</p>
<p>出于目的，我们将 tvOS 和 watchOS 视作 iOS 的子集，并具有相似的崩溃报告。</p>
<p>请注意，此处我们所指的“ iOS崩溃报告 ” 是用来表示来自物理设备的崩溃报告。 当发生崩溃时，我们通常是在模拟器上调试应用程序。在这种情况下，异常代码可能会有所不同，因为模拟器使用不同的方法来导致应用在调试器下停止。</p>
<h3 id="ios-崩溃报告-header-部分">iOS 崩溃报告 Header 部分</h3>
<p>崩溃报告通常以以下样式的开头：</p>
<pre><code>Incident Identifier: E030D9E4-32B5-4C11-8B39-C12045CABE26
CrashReporter Key:   b544a32d592996e0efdd7f5eaafd1f4164a2e13c
Hardware Model:      iPad6,3
Process:             icdab_planets [2726]
Path:                /private/var/containers/Bundle/Application/
BEF249D9-1520-40F7-93F4-8B99D913A4AC/
icdab_planets.app/icdab_planets
Identifier:          www.perivalebluebell.icdab-planets
Version:             1 (1.0)
Code Type:           ARM-64 (Native)
Role:                Foreground
Parent Process:      launchd [1]
Coalition:           www.perivalebluebell.icdab-planets [1935]</code></pre>
<p>这些类目由下表进行解释：</p>
<table>
<thead>
<tr class="header">
<th>Entry</th>
<th>Meaning</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Incident Identifier</td>
<td>崩溃报告的唯一编号</td>
</tr>
<tr class="even">
<td>CrashReporter Key</td>
<td>崩溃设备的唯一标识符</td>
</tr>
<tr class="odd">
<td>Hardware Model</td>
<td>Apple 硬件模型（iPad，iPhone）</td>
</tr>
<tr class="even">
<td>Process</td>
<td>崩溃的进程名称（或编号）</td>
</tr>
<tr class="odd">
<td>Path</td>
<td>设备文件系统上崩溃程序的完整路径名</td>
</tr>
<tr class="even">
<td>Identifier</td>
<td>来自<code>Info.plist</code> 的 Bundle identifier</td>
</tr>
<tr class="odd">
<td>Version</td>
<td><code>CFBundleVersion</code>；括号中有 <code>CFBundleVersionString</code></td>
</tr>
<tr class="even">
<td>Code Type</td>
<td>崩溃进程的目标体系结构</td>
</tr>
<tr class="odd">
<td>Role</td>
<td>进程 <code>task_role</code>。如果我们在后台、前台或控制台应用程序中，都会显示一个指示器。主要影响进程的调度优先级。</td>
</tr>
<tr class="even">
<td>Parent Process</td>
<td>崩溃进程的父级。<code>launchd</code> 是一个进程启动程序，通常是父进程。</td>
</tr>
<tr class="odd">
<td>Coalition</td>
<td>任务分组合并，然后他们就可以可以把资源消耗集中起来。</td>
</tr>
</tbody>
</table>
<blockquote>
<p><code>CFBundleVersion</code>表示 bundle 构建迭代的版本号(发布与未发布) 而<code>CFBundleVersionString</code> 可能想说的是<code>CFBundleShortVersionString</code> 表示 bundle 发布版本号</p>
</blockquote>
<p>首先要看的是版本。通常，如果我们是一个小团队或者是独立开发者，我们没有什么精力和资源去分析诊断旧版本的崩溃，所以首先我们要做的是让用户去更新最新版本。</p>
<p>如果我们有很多的崩溃，那么可能会出现一种现象。它可能来自于同一个用户（看到同一个 <code>CrashReporter Key</code>），也可能来自不同的用户（看到不同的 <code>CrashReporter Key</code>）。这可能影响我们对于崩溃的优先级判断。</p>
<p><code>Hardware Model</code> 是一个值得关注的点。是只有 iPad设备，还是仅限iPhone设备，或者两者兼而有之? 对于特定的设备，我们很少测试或者代码做了特殊处理，亦或者指向一个我们并没有测试过的老旧设备。</p>
<p>APP 是在前台还是在后台中奔溃也是一个值得关注的点，大多数应用程序通常都不会测试其在后台运行时会发生什么，这里说的是 <code>Role</code>。例如，我们可能会接到一个电话，或者在应用之间进行切换。</p>
<p>现有的<code>Code Type</code> 通常是 <code>64-bit ARM</code> 的。但是我们可能看到原始的 <code>32-bit ARM</code></p>
<h3 id="ios-崩溃报告-date-和-version-部分">iOS 崩溃报告 Date 和 Version 部分</h3>
<p>接下来崩溃报告将提供日期和版本信息：</p>
<pre><code>Date/Time:           2018-07-16 10:15:31.4746 +0100
Launch Time:         2018-07-16 10:15:31.3763 +0100
OS Version:          iPhone OS 11.3 (15E216)
Baseband Version:    n/a
Report Version:      104</code></pre>
<p>这些类目由下表进行解释：</p>
<table>
<colgroup>
<col style="width: 50%" />
<col style="width: 50%" />
</colgroup>
<thead>
<tr class="header">
<th>Entry</th>
<th>Meaning</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Date/Time</td>
<td>崩溃发生的时间</td>
</tr>
<tr class="even">
<td>Launch Time</td>
<td>崩溃前最初启动该进程的时间</td>
</tr>
<tr class="odd">
<td>OS Version</td>
<td>操作系统版本（内部版本号）。</td>
</tr>
<tr class="even">
<td>Baseband Version</td>
<td>蜂窝调制解调器固件版本号（用于电话呼叫）或 <code>n/a</code>（如果设备没有蜂窝调制解调器）（大多数 iPad，iPod Touch 等）</td>
</tr>
<tr class="odd">
<td>Report Version</td>
<td>生成报告的 ReportCrash 版本</td>
</tr>
</tbody>
</table>
<p>首先要检查的是操作系统的版本。比我们测试的版本新还是旧？是 <code>beta</code> 版吗？</p>
<p>接下来要比较启动时间和崩溃发生时的时间差值。应用程序是立即崩溃还是经过很长时间后崩溃？启动崩溃有时可能是打包和部署问题。我们将利用一些技术来解决运行以后的崩溃问题。</p>
<p>日期是有有意义？有时，设备在某个时间会设置或转发，这可能会触发安全证书或许可证密钥的日期检查。确保日期看起来是真实的。</p>
<p>通常关注 <code>Baseband Version</code> 并没有什么用。基带的存在意味着应用会被通话打断（当然，无论如何也会有 VOIP 呼叫）。iPad 软件通常被认为是不打算接听电话的，但 iPad 也可以选择购买有蜂窝调制解调器的版本。</p>
<h3 id="ios崩溃报告异常部分">iOS崩溃报告异常部分</h3>
<p>崩溃报告接下来将包含异常信息：</p>
<pre><code>Exception Type:  EXC_CRASH (SIGABRT)
Exception Codes: 0x0000000000000000, 0x0000000000000000
Exception Note:  EXC_CORPSE_NOTIFY
Triggered by Thread:  0</code></pre>
<p>或者它可能具有更详细的异常信息：</p>
<pre><code>Exception Type:  EXC_CRASH (SIGKILL)
Exception Codes: 0x0000000000000000, 0x0000000000000000
Exception Note:  EXC_CORPSE_NOTIFY
Termination Reason: Namespace &lt;0xF&gt;, Code 0xdead10cc
Triggered by Thread:  0</code></pre>
<p>这通常发生在，MachOS 内核在有问题的进程上引发了操作系统异常，从而终止了该进程。 然后，ReportCrash 程序从操作系统中检索此类异常的详细信息。</p>
<p>这些类目由下表进行解释：</p>
<table>
<colgroup>
<col style="width: 50%" />
<col style="width: 50%" />
</colgroup>
<thead>
<tr class="header">
<th>Entry</th>
<th>Meaning</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Exception Type</td>
<td>Mach OS中的异常类型</td>
</tr>
<tr class="even">
<td>Exception Codes</td>
<td>异常类型的编码，例如尝试访问无效的地址以及支持信息。</td>
</tr>
<tr class="odd">
<td>Exception Note</td>
<td>如果进程被看门狗计时器杀死，会显示<code>SIMULATED（这不是崩溃）</code>，进程崩溃则显示 <code>EXC_CORPSE_NOTIFY</code></td>
</tr>
<tr class="even">
<td>Termination Reason</td>
<td>视情况而定，它给出一个命名空间（数字或子系统名称）和一个 <code>magic number</code>（通常是一个看起来像英语单词的十六进制数字）。 有关每个终止代码的详细信息，请参见下文。</td>
</tr>
<tr class="odd">
<td>Triggered by Thread</td>
<td>导致崩溃的进程中的线程</td>
</tr>
</tbody>
</table>
<p>在本节中，最重要的项是异常类型。</p>
<table>
<colgroup>
<col style="width: 50%" />
<col style="width: 50%" />
</colgroup>
<thead>
<tr class="header">
<th>Exception Type</th>
<th>Meaning</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>EXC_CRASH (SIGABRT)</code></td>
<td>我们的程序触发了一个编程语言异常，例如失败的断言，这导致操作系统中止我们的应用程序</td>
</tr>
<tr class="even">
<td><code>EXC_CRASH (SIGQUIT)</code></td>
<td>一个进程从另一个正在管理它的进程接收到退出信号。通常，这意味着某个拓展程序花费了太长的时间或者消耗了太多的内存。App 的拓展程序仅能获得有限的内存。</td>
</tr>
<tr class="odd">
<td><code>EXC_CRASH (SIGKILL)</code></td>
<td>系统终止了我们的 App（或 App 的拓展程序），通常是因为已经达到了某种资源的限制。我们需要研究终止原因，以确定违反的某个政策是终止原因。</td>
</tr>
<tr class="even">
<td><code>EXC_BAD_ACCESS (SIGSEGV)</code> 或 <code>EXC_BAD_ACCESS (SIGBUS)</code></td>
<td>我们的程序很可能试图访问错误的或者是我们没有权限访问的内存地址。或由于内存压力，该内存已被释放</td>
</tr>
<tr class="odd">
<td><code>EXC_BREAKPOINT (SIGTRAP)</code></td>
<td>这可能是由于触发了一个<code>NSException</code>（可能是我们自己的库触发的）或者是调用了<code>_NSLockError</code> 或 <code>objc_exception_throw</code>方法。例如，这可能是因为 Swift 检测到异常，例如强制展开 nil 可选</td>
</tr>
<tr class="even">
<td><code>EXC_BAD_INSTRUCTION (SIGILL)</code></td>
<td>这是程序代码本身有问题，而不是因为错误的内存访问。 这在 iOS 设备上应该很少见； 可能是编译器或优化器错误，或者是错误的手写汇编代码。 但在模拟器上，是不一样的，因为使用未定义的操作码是 Swift 运行时用来停止访问僵尸对象（已分配对象）的一种技术。</td>
</tr>
<tr class="odd">
<td><code>EXC_GUARD</code></td>
<td>这个问题发生在程序去关闭一个受保护的文件。例如，关闭系统使用的 SQLite 库</td>
</tr>
</tbody>
</table>
<p>当存在终止原因时，我们可以按如下方式查找代码：</p>
<table>
<colgroup>
<col style="width: 50%" />
<col style="width: 50%" />
</colgroup>
<thead>
<tr class="header">
<th>Termination Code</th>
<th>Meaning</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>0xdead10cc</code></td>
<td>我们在挂起之前持有文件锁或 sqlite 数据库锁。我们应该在挂起之前释放锁。</td>
</tr>
<tr class="even">
<td><code>0xbaaaaaad</code></td>
<td>通过侧面和两个音量按钮对整个系统进行了 <code>stackshot</code>。请参阅前面的系统诊断部分</td>
</tr>
<tr class="odd">
<td><code>0xbad22222</code></td>
<td>可能是 VOIP 应用被频繁唤起导致的崩溃。也可以注意一下我们的后台调用网络的代码。 如果我们的TCP连接被唤醒太多次（例如 300 秒内唤醒 15 次），就会导致此崩溃。</td>
</tr>
<tr class="even">
<td><code>0x8badf00d</code></td>
<td>我们的应用程序执行状态更改（启动、关闭、处理系统消息等）花费了太长时间。与看门口的时间策略发生冲突（超时）并导致终止。最常见的罪魁祸首是在主线程上进行同步的网络连接。</td>
</tr>
<tr class="odd">
<td><code>0xc00010ff</code></td>
<td>系统检测的设备发烫而终止了我们的 App。如果只在少量设备上（几个）发生，那就可能是由于硬件的问题，而不是我们 App 问题。但是如果发生在其他设备上，我们应该使用 Instruments 去检查我们 App 的耗电量问题。</td>
</tr>
<tr class="even">
<td><code>0x2bad45ec</code></td>
<td>发生安全冲突。 如果 <code>Termination Description</code> 显示为 <code>Process detected doing insecure drawing while in secure mode</code>，则意味着我们的应用尝试在不允许的情况下进行绘制，例如在锁定屏幕的情况下。</td>
</tr>
</tbody>
</table>
<h4 id="magic-numbers-和他们的黑话意思">Magic Numbers 和他们的黑话(意思)</h4>
<p>出于某种怪异的幽默，在讨论终止代码时，下面的 <code>Magic Number</code> 是表达这些意思</p>
<table>
<thead>
<tr class="header">
<th>Magic Number</th>
<th>Spoken Phrase</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>0xdead10cc</code></td>
<td>Deadlock</td>
</tr>
<tr class="even">
<td><code>0xbaaaaaad</code></td>
<td>Bad</td>
</tr>
<tr class="odd">
<td><code>0xbad22222</code></td>
<td>Bad too (two) many times</td>
</tr>
<tr class="even">
<td><code>0x8badf00d</code></td>
<td>Ate (eight) bad food</td>
</tr>
<tr class="odd">
<td><code>0xc00010ff</code></td>
<td>Cool Off</td>
</tr>
<tr class="even">
<td><code>0x2bad45ec</code></td>
<td>Too bad for security</td>
</tr>
</tbody>
</table>
<h4 id="主动终止">主动终止</h4>
<p>当 <code>Exception Type</code> 为 <code>SIGABRT</code> 时，我们应该从崩溃堆栈中查找代码中存在哪些断言或异常。</p>
<h4 id="内存问题">内存问题</h4>
<p>当我们存在内存问题时， <code>Exception Type</code> 为 <code>EXC_BAD_ACCESS</code> 括号里是<code>SIGSEGV</code> 或者 <code>SIGBUS</code>，我们根据括号中的异常代码来判断是什么内存问题。对于这类问题，我们可以打开 Xcode 中关于特定 <code>target scheme</code> 的相关诊断程序。应该打开 <code>address sanitizer</code>，以查看是否可以发现错误。</p>
<blockquote>
<p>选择 <code>Edit Scheme</code> 选择 <code>Run</code> 选择 <code>Address Sanitizer</code></p>
</blockquote>
<p>如果 Xcode 显示 App 正在使用大量内存，那么可能是我们所依赖的内存已被系统释放。为此，请打开 <code>Malloc Stack</code> 日志记录选项，选择 <code>All Allocation And Free History</code>。然后在 App 运行的某个时刻，可以单击 <code>MemGraph</code> 按钮，然后探索对象的分配历史记录。</p>
<p>有关更多详细信息，请阅读 内存诊断 章节。</p>
<h4 id="例外">例外</h4>
<p>当我们的异常类型为 <code>EXC_BREAKPOINT</code> 时，这可能会造成一定的困惑。该应用在没有 <code>debugger</code> 工具的情况下独自运行，那么断点从哪里来呢？通常，可能是因为我们运行了 <code>NSException</code> 代码。这使的系统在过程中跟踪陷阱信号，并使任何可用的调试器都这个过程中以帮助调试。所以即使我们在 <code>debug</code> 时断开断点，我们也会在这里停住，这样我们就可以找出存在运行时异常的原因。在正常的应用程序运行的情况下，没有 <code>debugger</code> 工具，系统只能崩溃应用程序。</p>
<h4 id="非法指示">非法指示</h4>
<p>当我们的异常类型为<code>EXC_BAD_INSTRUCTION</code>时，异常代码（接下来的字符）将是有问题的汇编代码。这种情况应改是罕见的。这值得我们去调整 <code>Build Settings</code> 中代码的优先级别，因为更高级别的优化可能会导致在构建期间发出更多奇特的指令，从而增加了编译器错误的机会。或者说，问题可能出在代码中具有手动编译优化功能的底层库中，例如多媒体资源库。手写汇编指令可能是错误出现的原因。</p>
<h4 id="保护例外">保护例外</h4>
<p>有些操作系统会使用某些文件，因此它们会受到特殊保护。当关闭（或以其他方式修改）此类文件时，我们会得到一个 <code>EXC_GUARD</code> 类型的异常。</p>
<p>例如：</p>
<pre><code>Exception Type:  EXC_GUARD
Exception Codes: 0x0000000100000000, 0x08fd4dbfade2dead
Crashed Thread:  5</code></pre>
<p>异常代码 <code>0x08fd4dbfade2dead</code> 表达了修改与数据库相关的文件（在我们的示例中它已被关闭）。这个十六进制字符串可以类似 <code>黑话</code> 读作 <code>Ate (8) File Descriptor (fd) for (4) Database (db)</code>。</p>
<p>当出现这样的问题时，我们可以查看崩溃线程中的文件操作。 在我们的例子中：</p>
<pre><code>Thread 5 name:  Dispatch queue: com.apple.root.default-priority
Thread 5 Crashed:
0   libsystem_kernel.dylib          0x3a287294 close + 8
1   ExternalAccessory               
0x32951be2 -[EASession dealloc] + 226</code></pre>
<p>在这里执行了一个关闭操作。</p>
<p>当我们有与文件操作对应的描述代码时，我们应该特别检查一下我们的关闭操作代码。</p>
<p>我们可以从第一个异常代码推断文件操作。它是一个 64 位标志，指定如下：</p>
<table>
<thead>
<tr class="header">
<th>Bit Range</th>
<th>Meaning</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>63:61</td>
<td>Guard 类型，其中 0x2 表示文件描述符</td>
</tr>
<tr class="even">
<td>60:32</td>
<td>Flavor</td>
</tr>
<tr class="odd">
<td>31:00</td>
<td>File descriptor number</td>
</tr>
</tbody>
</table>
<p>从观察中，我们认为 Guard 类型没有被使用。</p>
<p>Flavor 是另一个向量：</p>
<table>
<thead>
<tr class="header">
<th>Flavor Bit</th>
<th>Meaning</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>0</td>
<td>尝试调用<code>close()</code></td>
</tr>
<tr class="even">
<td>1</td>
<td><code>dup()</code> , <code>dup2()</code> 或者 <code>fcntl()</code></td>
</tr>
<tr class="odd">
<td>2</td>
<td>通过套接字尝试调用<code>sendmsg()</code></td>
</tr>
<tr class="even">
<td>4</td>
<td>尝试调用<code>write()</code></td>
</tr>
</tbody>
</table>
<h3 id="ios-崩溃报告已过滤的-syslog-部分">iOS 崩溃报告已过滤的 <code>syslog</code> 部分</h3>
<p>奔溃报告接下来是 <code>syslog</code> 部分：</p>
<pre><code>Filtered syslog:
None found</code></pre>
<p>这是一个异常部分，因为他会去查看奔溃进程的进程 ID，然后查看该进程是否有任何 <code>syslog</code> （系统日志）部分。这个例子中我们并未在奔溃报告看到任何已过滤的信息，只看到 <code>None found</code> 。</p>
<h3 id="ios-崩溃报告中的异常回溯部分">iOS 崩溃报告中的异常回溯部分</h3>
<p>当我们的应用程序检测到问题并要求操作系统终止该应用程序时，我们将获得报告的异常回溯部分。这涵盖了自己主动或通过 Swift，Objective-C 或 C 运行时支持库间接调用了<code>abort</code>，<code>NSException</code>，<code>_NSLockError</code>或<code>objc_exception_throw</code>的情况。</p>
<p>我们并没有办法得到实际发生断言的部分。但我们可以假定已经过滤的系统日志的前一个部分应该完成了这部分。虽然，通过 <em>Window-&gt; Devices and Simulators-&gt; Open Console</em> 会允许我们恢复该信息。</p>
<p>当我们在客户的崩溃报告中看到异常回溯时，我们应该要求崩溃设备的控制台日志。</p>
<p>例如，我们将看到：</p>
<pre><code>default 13:36:58.000000 +0000   icdab_nsdata
     My data is &lt;&gt; - ok since we can handle a nil

default 13:36:58.000000 +0100   icdab_nsdata     
-[__NSCFConstantString _isDispatchData]:
unrecognized selector sent to instance 0x3f054

default 13:36:58.000000 +0100   icdab_nsdata
     *** Terminating app due to
uncaught exception &#39;NSInvalidArgumentException&#39;, reason:
&#39;-[__NSCFConstantString _isDispatchData]:
 unrecognized selector sent to
instance 0x3f054&#39;
    *** First throw call stack:
    (0x25aa391b 0x2523ee17 0x25aa92b5 0x25aa6ee1 0x259d2238
     0x2627e9a5 0x3d997
    0x2a093785 0x2a2bb2d1 0x2a2bf285 0x2a2d383d 0x2a2bc7b3
     0x27146c07
    0x27146ab9 0x27146db9 0x25a65dff 0x25a659ed 0x25a63d5b
     0x259b3229
     0x259b3015 0x2a08cc3d 0x2a087189 0x3d80d 0x2565b873)

default 13:36:58.000000 +0100   SpringBoard  Application
&#39;UIKitApplication:www.perivalebluebell.icdab-nsdata[0x51b9]&#39;
 crashed.

default 13:36:58.000000 +0100   UserEventAgent
     2769630555571:
 id=www.perivalebluebell.icdab-nsdata pid=386, state=0

default 13:36:58.000000 +0000   ReportCrash  Formulating
report for corpse[386] icdab_nsdata

default 13:36:58.000000 +0000   ReportCrash  Saved type
 &#39;109(109_icdab_nsdata)&#39;
 report (2 of max 25) at
 /var/mobile/Library/Logs/CrashReporter/
 icdab_nsdata-2018-07-27-133658.ips</code></pre>
<p>有趣的是这一行：</p>
<pre><code>&#39;-[__NSCFConstantString _isDispatchData]:
unrecognized selector sent to instance 0x3f054&#39;</code></pre>
<p>这意味着向 <code>NSString</code> 类发送了<code>_isDispatchData</code> 方法。不存在的方法。</p>
<p>在崩溃报告中看到匹配的异常回溯是：</p>
<pre><code>Last Exception Backtrace:
0   CoreFoundation                  
0x25aa3916 __exceptionPreprocess + 122
1   libobjc.A.dylib                 
0x2523ee12 objc_exception_throw + 33
2   CoreFoundation                  0x25aa92b0
-[NSObject+ 1045168 (NSObject) doesNotRecognizeSelector:] + 183
3   CoreFoundation                  
0x25aa6edc ___forwarding___ + 695
4   CoreFoundation                  
0x259d2234 _CF_forwarding_prep_0 + 19
5   Foundation                      0x2627e9a0
-[_NSPlaceholderData initWithData:] + 123
6   icdab_nsdata                    0x000f89ba
-[AppDelegate application:didFinishLaunchingWithOptions:]
 + 27066 (AppDelegate.m:26)
7   UIKit                           0x2a093780
-[UIApplication
 _handleDelegateCallbacksWithOptions:isSuspended:restoreState:]
 + 387
8   UIKit                           0x2a2bb2cc
-[UIApplication
 _callInitializationDelegatesForMainScene:transitionContext:]
 + 3075
9   UIKit                           0x2a2bf280
-[UIApplication
 _runWithMainScene:transitionContext:completion:] + 1583
10  UIKit                           0x2a2d3838
__84-[UIApplication
 _handleApplicationActivationWithScene:transitionContext:
completion:]_block_invoke3286 + 31
11  UIKit                           0x2a2bc7ae
-[UIApplication workspaceDidEndTransaction:] + 129
12  FrontBoardServices              0x27146c02
__FBSSERIALQUEUE_IS_CALLING_OUT_TO_A_BLOCK__ + 13
13  FrontBoardServices              0x27146ab4
-[FBSSerialQueue _performNext] + 219
14  FrontBoardServices              0x27146db4
-[FBSSerialQueue _performNextFromRunLoopSource] + 43
15  CoreFoundation                  0x25a65dfa
__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 9
16  CoreFoundation                  
0x25a659e8 __CFRunLoopDoSources0 + 447
17  CoreFoundation                  
0x25a63d56 __CFRunLoopRun + 789
18  CoreFoundation                  
0x259b3224 CFRunLoopRunSpecific + 515
19  CoreFoundation                  
0x259b3010 CFRunLoopRunInMode + 103
20  UIKit                           
0x2a08cc38 -[UIApplication _run] + 519
21  UIKit                           
0x2a087184 UIApplicationMain + 139
22  icdab_nsdata                    
0x000f8830 main + 26672 (main.m:14)
23  libdyld.dylib                   
0x2565b86e tlv_get_addr + 41</code></pre>
<p>此回溯的格式与线程回溯的格式相同，稍后将进行介绍。</p>
<p>异常回溯部分的目的是提供比崩溃线程提供的更多的细节。</p>
<p>在上述情况下崩溃的线程有线程回溯：</p>
<pre><code>Thread 0 name:  Dispatch queue: com.apple.main-thread
Thread 0 Crashed:
0   libsystem_kernel.dylib          0x2572ec5c __pthread_kill + 8
1   libsystem_pthread.dylib         0x257d8732 pthread_kill + 62
2   libsystem_c.dylib               0x256c30ac abort + 108
3   libc++abi.dylib                 0x2521aae4 __cxa_bad_cast + 0
4   libc++abi.dylib                 0x2523369e
default_terminate_handler+ 104094 () + 266
5   libobjc.A.dylib                 0x2523f0b0
_objc_terminate+ 28848 () + 192
6   libc++abi.dylib                 0x25230e16
std::__terminate(void (*)+ 93718 ()) + 78
7   libc++abi.dylib                 0x252308f8
__cxa_increment_exception_refcount + 0
8   libobjc.A.dylib                 
0x2523ef5e objc_exception_rethrow + 42
9   CoreFoundation                  
0x259b32ae CFRunLoopRunSpecific + 654
10  CoreFoundation                  
0x259b3014 CFRunLoopRunInMode + 108
11  UIKit                           
0x2a08cc3c -[UIApplication _run] + 524
12  UIKit                           
0x2a087188 UIApplicationMain + 144
13  icdab_nsdata                    
0x000f8834 main + 26676 (main.m:14)
14  libdyld.dylib                   
0x2565b872 start + 2</code></pre>
<p>如果只有线程回溯，我们将知道存在强制转换问题 <code>__cxa_bad_cast</code> ，但仅此而已。</p>
<p>从网上搜到 <code>NSData</code>具有一个私有的辅助类 <code>_NSPlaceholderData</code>。</p>
<p>这个情况是在一个需要使用 <code>NSData</code> 的地方使用了 <code>NSString</code> 对象。</p>
<h3 id="ios-崩溃报告线程部分">iOS 崩溃报告线程部分</h3>
<p>崩溃报告接下来是线程回溯的部分（为便于演示而进行了格式化）</p>
<pre><code>Thread 0 name:  Dispatch queue: com.apple.main-thread
Thread 0 Crashed:
0   libsystem_kernel.dylib          0x0000000183a012ec
 __pthread_kill + 8
1   libsystem_pthread.dylib         0x0000000183ba2288
 pthread_kill$VARIANT$mp + 376
2   libsystem_c.dylib               0x000000018396fd0c
 abort + 140
3   libsystem_c.dylib               0x0000000183944000
 basename_r + 0
4   icdab_planets                   
0x0000000104e145bc
 -[PlanetViewController viewDidLoad] + 17852
  (PlanetViewController.mm:33)
5   UIKit                           0x000000018db56ee0
 -[UIViewController loadViewIfRequired] + 1020
6   UIKit                           0x000000018db56acc
 -[UIViewController view] + 28
7   UIKit                           0x000000018db47d60
 -[UIWindow addRootViewControllerViewIfPossible] + 136
8   UIKit                           0x000000018db46b94
 -[UIWindow _setHidden:forced:] + 272
9   UIKit                           0x000000018dbd46a8
-[UIWindow makeKeyAndVisible] + 48
10  UIKit                           0x000000018db4a2f0
 -[UIApplication
  _callInitializationDelegatesForMainScene:transitionContext:]
  + 3660
11  UIKit                           0x000000018db1765c
-[UIApplication
_runWithMainScene:transitionContext:completion:] + 1680
12  UIKit                           0x000000018e147a0c
__111-[__UICanvasLifecycleMonitor_Compatability
_scheduleFirstCommitForScene:transition:firstActivation:
completion:]_block_invoke + 784
13  UIKit                           0x000000018db16e4c
+[_UICanvas _enqueuePostSettingUpdateTransactionBlock:] + 160
14  UIKit                           0x000000018db16ce8
-[__UICanvasLifecycleMonitor_Compatability
_scheduleFirstCommitForScene:transition:
firstActivation:completion:] + 240
15  UIKit                           0x000000018db15b78
-[__UICanvasLifecycleMonitor_Compatability
activateEventsOnly:withContext:completion:] + 724
16  UIKit                           0x000000018e7ab72c
__82-[_UIApplicationCanvas
 _transitionLifecycleStateWithTransitionContext:
completion:]_block_invoke + 296
17  UIKit                           0x000000018db15268
-[_UIApplicationCanvas
 _transitionLifecycleStateWithTransitionContext:
completion:] + 432
18  UIKit                           0x000000018e5909b8
__125-[_UICanvasLifecycleSettingsDiffAction
performActionsForCanvas:
withUpdatedScene:settingsDiff:fromSettings:
transitionContext:]_block_invoke + 220
19  UIKit                           0x000000018e6deae8
_performActionsWithDelayForTransitionContext + 112
20  UIKit                           0x000000018db14c88
-[_UICanvasLifecycleSettingsDiffAction performActionsForCanvas:
withUpdatedScene:settingsDiff:fromSettings:
transitionContext:] + 248
21  UIKit                           0x000000018db14624
-[_UICanvas
scene:didUpdateWithDiff:transitionContext:completion:] + 368
22  UIKit                           0x000000018db1165c
-[UIApplication workspace:didCreateScene:withTransitionContext:
completion:] + 540
23  UIKit                           0x000000018db113ac
-[UIApplicationSceneClientAgent scene:didInitializeWithEvent:
completion:] + 364
24  FrontBoardServices              0x0000000186778470
-[FBSSceneImpl
_didCreateWithTransitionContext:completion:] + 364
25  FrontBoardServices              0x0000000186780d6c
__56-[FBSWorkspace client:handleCreateScene:withCompletion:]
_block_invoke_2 + 224
26  libdispatch.dylib               0x000000018386cae4
_dispatch_client_callout + 16
27  libdispatch.dylib               0x00000001838741f4
_dispatch_block_invoke_direct$VARIANT$mp + 224
28  FrontBoardServices              0x00000001867ac878
__FBSSERIALQUEUE_IS_CALLING_OUT_TO_A_BLOCK__ + 36
29  FrontBoardServices              0x00000001867ac51c
-[FBSSerialQueue _performNext] + 404
30  FrontBoardServices              0x00000001867acab8
-[FBSSerialQueue _performNextFromRunLoopSource] + 56
31  CoreFoundation                  0x0000000183f23404
__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 24
32  CoreFoundation                  0x0000000183f22c2c
__CFRunLoopDoSources0 + 276
33  CoreFoundation                  0x0000000183f2079c
 __CFRunLoopRun + 1204
34  CoreFoundation                  0x0000000183e40da8
CFRunLoopRunSpecific + 552
35  GraphicsServices                0x0000000185e23020
 GSEventRunModal + 100
36  UIKit                           0x000000018de2178c
 UIApplicationMain + 236
37  icdab_planets                   0x0000000104e14c94
 main + 19604 (main.m:14)
38  libdyld.dylib                   0x00000001838d1fc0
 start + 4

Thread 1:
0   libsystem_pthread.dylib         0x0000000183b9fb04
 start_wqthread + 0

Thread 2:
0   libsystem_kernel.dylib          0x0000000183a01d84
 __workq_kernreturn + 8
1   libsystem_pthread.dylib         0x0000000183b9feb4
 _pthread_wqthread + 928
2   libsystem_pthread.dylib         0x0000000183b9fb08
 start_wqthread + 4

Thread 3:
0   libsystem_pthread.dylib         0x0000000183b9fb04
start_wqthread + 0

Thread 4:
0   libsystem_kernel.dylib          0x0000000183a01d84
 __workq_kernreturn + 8
1   libsystem_pthread.dylib         0x0000000183b9feb4
 _pthread_wqthread + 928
2   libsystem_pthread.dylib         0x0000000183b9fb08
start_wqthread + 4

Thread 5:
0   libsystem_kernel.dylib          0x0000000183a01d84
 __workq_kernreturn + 8
1   libsystem_pthread.dylib         0x0000000183b9feb4
 _pthread_wqthread + 928
2   libsystem_pthread.dylib         0x0000000183b9fb08
 start_wqthread + 4

Thread 6 name:  com.apple.uikit.eventfetch-thread
Thread 6:
0   libsystem_kernel.dylib          0x00000001839dfe08
 mach_msg_trap + 8
1   libsystem_kernel.dylib          0x00000001839dfc80
 mach_msg + 72
2   CoreFoundation                  0x0000000183f22e40
__CFRunLoopServiceMachPort + 196
3   CoreFoundation                  0x0000000183f20908
 __CFRunLoopRun + 1568
4   CoreFoundation                  0x0000000183e40da8
CFRunLoopRunSpecific + 552
5   Foundation                      0x00000001848b5674
-[NSRunLoop+ 34420 (NSRunLoop) runMode:beforeDate:] + 304
6   Foundation                      0x00000001848b551c
-[NSRunLoop+ 34076 (NSRunLoop) runUntilDate:] + 148
7   UIKit                           0x000000018db067e4
-[UIEventFetcher threadMain] + 136
8   Foundation                      0x00000001849c5efc
__NSThread__start__ + 1040
9   libsystem_pthread.dylib         0x0000000183ba1220
 _pthread_body + 272
10  libsystem_pthread.dylib         0x0000000183ba1110
 _pthread_body + 0
11  libsystem_pthread.dylib         0x0000000183b9fb10
 thread_start + 4

Thread 7:
0   libsystem_pthread.dylib         0x0000000183b9fb04
 start_wqthread + 0</code></pre>
<p>崩溃报告将明确告诉我们哪个线程崩溃了。</p>
<pre><code>Thread 0 Crashed:</code></pre>
<p>线程有编号，并且如果线程有名称的话，也会展示名称：</p>
<pre><code>Thread 0 name:  Dispatch queue: com.apple.main-thread</code></pre>
<p>我们应该将的大部分精力放在崩溃的线程上。 通常是线程 0。注意崩溃线程的名称。请注意，不能在主线程<code>com.apple.main-thread</code> 上执行诸如网络之类的长时间任务，因为该线程用于处理用户交互。</p>
<p>对 <code>__workq_kernreturn</code> 的引用仅表示正在等待工作的线程，因此除非有大量线程，否则可以将其忽略。</p>
<p>同样，对 <code>mach_msg_trap</code> 的引用仅指示线程正在等待消息进入。</p>
<p>当查看堆栈回溯时，首先出现堆栈帧 0，即堆栈的顶部，然后列出调用所有调用栈。 因此，最后一件事是在第 0 帧。</p>
<blockquote>
<p>栈帧：每一次函数的调用,都会在调用栈（call stack）上维护一个独立的栈帧（stack frame）。</p>
</blockquote>
<h4 id="堆栈回溯项">堆栈回溯项</h4>
<p>现在让我们关注每个线程的回溯项。 例如：</p>
<pre><code>20  UIKit                           0x000000018db14c88
-[_UICanvasLifecycleSettingsDiffAction
performActionsForCanvas:
withUpdatedScene:settingsDiff:fromSettings:
transitionContext:] + 248</code></pre>
<table>
<thead>
<tr class="header">
<th>Column</th>
<th>Meaning</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>1</td>
<td>堆栈帧号，最近执行的是 0。</td>
</tr>
<tr class="even">
<td>2</td>
<td>二进制文件执行。</td>
</tr>
<tr class="odd">
<td>3</td>
<td>执行位置（第 0 帧）或返回位置（第 1 帧以后）</td>
</tr>
<tr class="even">
<td>4+</td>
<td>符号化函数名称或函数中具有偏移量的地址</td>
</tr>
</tbody>
</table>
<p>帧数越多，就使我们在程序执行顺序方面的时间倒退。 堆栈的顶部或最近运行的代码位于第 0 帧。用有意义的函数名编写代码的原因之一是调用堆栈从概念上描述了正在发生的事情。 使用小型单一用途功能的方法是一种好习惯。 它满足了诊断和可维护性的需求。</p>
<p>堆栈回溯项的第二列是二进制文件。我们主要关注自己的二进制代码，因为 Apple 的框架代码通常非常可靠。错误通常直接出现在我们的代码中，或者是由于错误使用 Apple API 引起的错误引起的。仅仅因为在 Apple 提供的代码中崩溃并不意味着该错误在 Apple 代码中。</p>
<p>第三栏，执行位置，有些棘手。 如果是第 0 帧，则它是代码中正在运行的实际位置。 如果用于任何后续帧，则它是代码中一旦子功能返回后将恢复的位置。</p>
<p>第四列是运行代码的站点（对于第 0 帧），或者正在进行函数调用的站点（对于以后的帧）。 对于符号化的崩溃，我们将看到地址的符号形式。 这将包括从函数开始到调用子函数的代码的位置偏移。 如果我们只有短函数，则此偏移量将是一个很小的值。 这意味着执行诊断时所需的单步执行代码要少得多，或者要读取的汇编代码要少得多。 这是保持我们的职能简短的另一个原因。 如果没有用符号表示崩溃，那么我们将只看到一个内存地址值。</p>
<p>因此，对于示例堆栈帧，我们有：</p>
<ul>
<li>堆栈帧 20</li>
<li>UIKit 二进制文件。</li>
<li><code>0x000000018db14c88</code> 返回 0-19 帧后的地址。</li>
<li>调用位置是从方法 <code>performActionsForCanvas</code> 开始的第248字节</li>
<li>类是 <code>_UICanvasLifecycleSettingsDiffAction</code></li>
</ul>
<h3 id="ios-崩溃报告线程状态部分">iOS 崩溃报告线程状态部分</h3>
<p>iOS 崩溃报告将来自 ARM-64 二进制文件（最常见）或传统 ARM 32 位二进制文件。</p>
<p>在两种情况下，我们都会获得类似的描述 ARM 寄存器状态的信息。</p>
<p>需要注意的一件事是特殊的十六进制代码，<code>0xbaddc0dedeadbead</code> 这意味着一个非初始化的指针。</p>
<h4 id="位线程状态">32 位线程状态</h4>
<pre><code>Thread 0 crashed with ARM Thread State (32-bit):
    r0: 0x00000000    r1: 0x00000000      r2: 0x00000000
          r3: 0x00000000
    r4: 0x00000006    r5: 0x3c42f000      r6: 0x3b66d304
          r7: 0x002054c8
    r8: 0x14d5f480    r9: 0x252348fd     r10: 0x90eecad7
         r11: 0x14d5f4a4
    ip: 0x00000148    sp: 0x002054bc      lr: 0x257d8733
          pc: 0x2572ec5c
  cpsr: 0x00000010</code></pre>
<h4 id="位线程状态-1">64 位线程状态</h4>
<pre><code>Thread 0 crashed with ARM Thread State (64-bit):
    x0: 0x0000000000000028   x1: 0x0000000000000029
       x2: 0x0000000000000008
       x3: 0x0000000183a4906c
    x4: 0x0000000104440260   x5: 0x0000000000000047
       x6: 0x000000000000000a
       x7: 0x0000000138819df0
    x8: 0x0000000000000000   x9: 0x0000000000000000
      x10: 0x0000000000000003
      x11: 0xbaddc0dedeadbead
   x12: 0x0000000000000012  x13: 0x0000000000000002
     x14: 0x0000000000000000
     x15: 0x0000010000000100
   x16: 0x0000000183b9b8cc  x17: 0x0000000000000100
     x18: 0x0000000000000000
     x19: 0x00000001b5c241c8
   x20: 0x00000001c0071b00  x21: 0x0000000000000018
     x22: 0x000000018e89b27a
     x23: 0x0000000000000000
   x24: 0x00000001c4033d60  x25: 0x0000000000000001
     x26: 0x0000000000000288
     x27: 0x00000000000000e0
   x28: 0x0000000000000010   fp: 0x000000016bde54b0
      lr: 0x000000010401ca04
    sp: 0x000000016bde53e0   pc: 0x000000010401c6c8
     cpsr: 0x80000000</code></pre>
<h3 id="ios-崩溃报告的-binary-images-部分">iOS 崩溃报告的 Binary Images 部分</h3>
<p>奔溃报告会有一部分列举了崩溃进程加载的所有 Binary Images。这通常是一串很长的列表。它强调了一个事实，即我们的应用程序有许多支持框架。大多数框架是私有框架。iOS 开发工具包似乎包含了大量 API，但这只是冰山一角。</p>
<p>这是一个示例列表，为便于演示而进行了编辑：</p>
<pre><code>Binary Images:

0x104018000 - 0x10401ffff icdab_as arm64
  &lt;b82579f401603481990d1c1c9a42b773&gt;
/var/containers/Bundle/Application/
1A05BC59-491C-4D0A-B4F6-8A98A804F74D/icdab_as.app/icdab_as

0x104030000 - 0x104037fff libswiftCoreFoundation.dylib arm64
  &lt;81f66e04bab133feb3369b4162a68afc&gt;
  /var/containers/Bundle/Application/
1A05BC59-491C-4D0A-B4F6-8A98A804F74D/icdab_as.app/
Frameworks/libswiftCoreFoundation.dylib


0x104044000 - 0x104057fff libswiftCoreGraphics.dylib arm64
  &lt;f1f2287fb5153a28beea12ec2d547bf8&gt;
  /var/containers/Bundle/Application/
1A05BC59-491C-4D0A-B4F6-8A98A804F74D/icdab_as.app/
Frameworks/libswiftCoreGraphics.dylib

0x104078000 - 0x10407ffff libswiftCoreImage.dylib arm64
  &lt;9433fc53f72630dc8c53851703dd440b&gt;
  /var/containers/Bundle/Application/
1A05BC59-491C-4D0A-B4F6-8A98A804F74D/icdab_as.app/
Frameworks/libswiftCoreImage.dylib

0x104094000 - 0x1040cffff dyld arm64
  &lt;06dc98224ae03573bf72c78810c81a78&gt; /usr/lib/dyld</code></pre>
<p>第一部分是图像已加载到内存中的位置。这里 <code>icdab_as</code> 已被加载到 <code>0x104018000</code>-<code>0x10401ffff</code> 范围内。</p>
<p>第二部分是二进制文件的名称。这里的名字是 <code>icdab_as</code>。</p>
<p>第三部分是加载的二进制文件中的体系结构切片。我们通常希望在这里看到 <code>arm64</code>（ARM 64位）。</p>
<p>第四部分是二进制文件的UUID。这里 <code>icdab_as</code> 的UDID 是 <code>b82579f401603481990d1c1c9a42b773</code>。</p>
<p>如果我们的 DSYM 文件 UUID 与二进制文件不匹配，则符号化将失败。</p>
<p>以下是使用 <code>dwarfdump</code> 命令在 DSYM 和应用程序二进制文件中看到的相应 UUID 的示例：</p>
<pre><code>$ dwarfdump --uuid icdab_as.app/icdab_as
icdab_as.app.dSYM/Contents/Resources/DWARF/icdab_as

UUID: 25BCB4EC-21DE-3CE6-97A8-B759F31501B7
 (arm64) icdab_as.app/icdab_as

UUID: 25BCB4EC-21DE-3CE6-97A8-B759F31501B7
 (arm64)
icdab_as.app.dSYM/Contents/Resources/DWARF/icdab_as</code></pre>
<p>第五部分是设备上显示的二进制文件的路径。</p>
<p>大多数二进制文件都有一个不言自明的名称。<code>dyld</code> 二进制文件是动态加载器。它位于所有堆栈回溯的底部，因为它负责在执行之前开始加载二进制文件。</p>
<p>动态加载器在准备二进制文件以执行时执行许多任务。如果我们的二进制引用了其他库，它将加载它们。如果没有，则无法加载我们的应用程序。这就是为什么即使在调用 <code>main.m</code> 中的任何代码之前也可能发生崩溃的原因。稍后，我们将研究如何诊断这些问题。</p>
<h2 id="macos-崩溃报告导览">macOS 崩溃报告导览</h2>
<p>尽管 macOS CrashReport 和 iOS CrashReport 是截然不同的程序，但 macOS 崩溃报告类似于 iOS 崩溃报告。为了避免重复，这里我们只强调与 iOS 的显着差异。</p>
<h3 id="macos-崩溃报告-header-部分">macOS 崩溃报告 Header 部分</h3>
<p>崩溃报告以一下部分开头：</p>
<pre><code>Process:               SiriNCService [1045]
Path:                  /System/Library/CoreServices/Siri.app/
Contents/XPCServices/SiriNCService.xpc/
Contents/MacOS/SiriNCService
Identifier:            com.apple.SiriNCService
Version:               146.4.5.1 (146.4.5.1)
Build Info:            AssistantUIX-146004005001000~1
Code Type:             X86-64 (Native)
Parent Process:        ??? [1]
Responsible:           Siri [863]
User ID:               501</code></pre>
<p>这里我们看到了熟悉的描述故障二进制信息。崩溃的进程是 SiriNCService，负责这个进程的是 Siri。在 Siri 和 SiriNCService 之间的跨进程通信时发生了崩溃（XPC）。</p>
<p>通常 iOS以一个用户身份运行用户体验的系统，然而 macOS系统却暴露出系统中存在多个用户 ID 的事实。</p>
<h3 id="macos-崩溃报告-date-和-version-部分">macOS 崩溃报告 Date 和 Version 部分</h3>
<p>接下来我们来看版本信息：</p>
<pre><code>Date/Time:             2018-06-24 09:52:01.419 +0100
OS Version:            Mac OS X 10.13.5 (17F77)
Report Version:        12
Anonymous UUID:        00CC683B-425F-ABF0-515A-3ED73BACDDB5

Sleep/Wake UUID:       10AE8838-17A9-4405-B03D-B680DDC84436
</code></pre>
<p><code>Anonymous UUID</code> 将是计算机的唯一标识。 <code>Sleep/Wake UUID</code> 用于匹配睡眠和唤醒事件。唤醒失败是系统崩溃的常见原因（与我们讨论的应用程序崩溃相反）。可以使用电源管理命令 <code>pmset</code> 获取更多信息。</p>
<h3 id="macos-持续时间部分">macOS 持续时间部分</h3>
<p>macOS 崩溃报告显示应用程序崩溃发生的时间。</p>
<pre><code>Time Awake Since Boot: 100000 seconds
Time Since Wake:       2000 seconds</code></pre>
<p>我们使用它作为一个广泛的指示只因为看到的数字总是四舍五入所得到一个方便的数字。</p>
<h3 id="macos崩溃报告系统完整性部分">macOS崩溃报告系统完整性部分</h3>
<pre><code>System Integrity Protection: enabled</code></pre>
<p>默认情况下，现代 macOS 运行是 “rootless”的。这意味着即使我们以超级用户身份登录，我们也无法更改系统的二进制文件。这些都是通过固件进行保护的。可以在禁用系统完整性保护的情况下启动 macOS。如果我们只是在禁用 SIP 的情况下崩溃，那么我们需要问为什么 SIP 会关闭以及对操作系统做了哪些更改。</p>
<h3 id="macos-崩溃报告的异常部分">macOS 崩溃报告的异常部分</h3>
<p>接下来展示异常部分。</p>
<pre><code>Crashed Thread:        0  Dispatch queue: com.apple.main-thread

Exception Type:        EXC_BAD_ACCESS (SIGSEGV)
Exception Codes:       KERN_INVALID_ADDRESS at 0x0000000000000018
Exception Note:        EXC_CORPSE_NOTIFY

Termination Signal:    Segmentation fault: 11
Termination Reason:    Namespace SIGNAL, Code 0xb
Terminating Process:   exc handler [0]

VM Regions Near 0x18:
--&gt;
    __TEXT                 0000000100238000-0000000100247000
     [   60K] r-x/rwx SM=COW  
     /System/Library/CoreServices/Siri.app/
     Contents/XPCServices/SiriNCService.xpc/Contents/MacOS/
     SiriNCService

Application Specific Information:
objc_msgSend() selector name: didUnlockScreen:</code></pre>
<p>这与 iOS 类似。但是，我们应该注意，如果我们在模拟器上重现 iOS 崩溃，那么模拟器可能会以不同方式对相同的编程错误进行建模。我们可以在 x86 硬件上获得与 ARM 对应的异常。</p>
<p>考虑以下代码，设置为旧版的手动引用计数（MRC）而不是自动引用计数（ARC）。</p>
<pre><code>void use_sema() {
    dispatch_semaphore_t aSemaphore =
     dispatch_semaphore_create(1);
    dispatch_semaphore_wait(aSemaphore, DISPATCH_TIME_FOREVER);
    dispatch_release(aSemaphore);
}</code></pre>
<p>代码会导致崩溃，因为在等待时会手动释放信号量。</p>
<p>当它在 ARM 硬件上的 iOS 模拟器上运行时，会发生崩溃</p>
<pre><code>Exception Type:  EXC_BREAKPOINT (SIGTRAP)
Exception Codes: 0x0000000000000001, 0x00000001814076b8
Termination Signal: Trace/BPT trap: 5
Termination Reason: Namespace SIGNAL, Code 0x5
Terminating Process: exc handler [0]
Triggered by Thread:  0

Application Specific Information:
BUG IN CLIENT OF LIBDISPATCH: Semaphore object deallocated while
 in use
 Abort Cause 1</code></pre>
<p>当它在 iOS 模拟器上运行时，我们会附带调试器</p>
<pre><code>Thread 1: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)</code></pre>
<p>模拟器使用错误的汇编指令来触发崩溃。</p>
<p>此外，如果我们编写一个运行相同代码的 macOS 应用程序，我们就会崩溃：</p>
<pre><code>Crashed Thread:        0  Dispatch queue: com.apple.main-thread

Exception Type:        EXC_BAD_INSTRUCTION (SIGILL)
Exception Codes:       0x0000000000000001, 0x0000000000000000
Exception Note:        EXC_CORPSE_NOTIFY

Termination Signal:    Illegal instruction: 4
Termination Reason:    Namespace SIGNAL, Code 0x4
Terminating Process:   exc handler [0]

Application Specific Information:
BUG IN CLIENT OF LIBDISPATCH:
Semaphore object deallocated while in use</code></pre>
<p>这就带来一个信息，当通过模拟器或等效的 macOS 代码在 x8 6硬件上再现 iOS ARM 崩溃时，由于运行时环境有所不同，表现也会稍有不同。</p>
<p>幸运的是，在两个崩溃报告中都很明显的表明了信号量被释放了。</p>
<h3 id="macos-崩溃报告线程部分">macOS 崩溃报告线程部分</h3>
<p>接下来是线程部分。这部分类似于iOS。</p>
<p>接下来用示例说明 macOS 崩溃报告中的线程部分：</p>
<pre><code>Thread 0 Crashed:: Dispatch queue: com.apple.main-thread
0   libobjc.A.dylib                 
0x00007fff69feae9d objc_msgSend + 29
1   com.apple.CoreFoundation        0x00007fff42e19f2c
 __CFNOTIFICATIONCENTER_IS_CALLING_OUT_TO_AN_OBSERVER__ + 12
2   com.apple.CoreFoundation        0x00007fff42e19eaf
___CFXRegistrationPost_block_invoke + 63
3   com.apple.CoreFoundation        0x00007fff42e228cc
 __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__ + 12
4   com.apple.CoreFoundation        0x00007fff42e052a3
__CFRunLoopDoBlocks + 275
5   com.apple.CoreFoundation        0x00007fff42e0492e
__CFRunLoopRun + 1278
6   com.apple.CoreFoundation        0x00007fff42e041a3
CFRunLoopRunSpecific + 483
7   com.apple.HIToolbox             0x00007fff420ead96
RunCurrentEventLoopInMode + 286
8   com.apple.HIToolbox             0x00007fff420eab06
ReceiveNextEventCommon + 613
9   com.apple.HIToolbox             0x00007fff420ea884
 _BlockUntilNextEventMatchingListInModeWithFilter + 64
10  com.apple.AppKit                0x00007fff4039ca73
_DPSNextEvent + 2085
11  com.apple.AppKit                0x00007fff40b32e34
-[NSApplication(NSEvent) _nextEventMatchingEventMask:
untilDate:inMode:dequeue:] + 3044
12  com.apple.ViewBridge            0x00007fff67859df0
-[NSViewServiceApplication nextEventMatchingMask:
untilDate:inMode:dequeue:] + 92
13  com.apple.AppKit                0x00007fff40391885
-[NSApplication run] + 764
14  com.apple.AppKit                0x00007fff40360a72
NSApplicationMain + 804
15  libxpc.dylib                    0x00007fff6af6cdc7
 _xpc_objc_main + 580
16  libxpc.dylib                    0x00007fff6af6ba1a
 xpc_main + 433
17  com.apple.ViewBridge            0x00007fff67859c15
-[NSXPCSharedListener resume] + 16
18  com.apple.ViewBridge            0x00007fff67857abe
 NSViewServiceApplicationMain + 2903
19  com.apple.SiriNCService         0x00000001002396e0
 main + 180
20  libdyld.dylib                   0x00007fff6ac12015
 start + 1</code></pre>
<h3 id="macos-崩溃报告线程状态部分">macOS 崩溃报告线程状态部分</h3>
<p>macOS 崩溃报告显示了崩溃线程中 X86 寄存器的详细信息。</p>
<pre><code>Thread 0 crashed with X86 Thread State (64-bit):
  rax: 0x0000600000249bd0  rbx: 0x0000600000869ac0
    rcx: 0x00007fe798f55320
    rdx: 0x0000600000249bd0
  rdi: 0x00007fe798f55320  rsi: 0x00007fff642de919
    rbp: 0x00007ffeef9c6220
    rsp: 0x00007ffeef9c6218
   r8: 0x0000000000000000   r9: 0x21eb0d26c23ae422
     r10: 0x0000000000000000
     r11: 0x00007fff642de919
  r12: 0x00006080001e8700  r13: 0x0000600000869ac0
    r14: 0x0000600000448910
    r15: 0x0000600000222e60
  rip: 0x00007fff69feae9d  rfl: 0x0000000000010246
    cr2: 0x0000000000000018

Logical CPU:     2
Error Code:      0x00000004
Trap Number:     14</code></pre>
<p>除与了 iOS 相似的信息之外，我们还获得了更多有关运行该线程的 CPU 的消息 。如果有需要我们可以在 Darwin XNU 源代码中查找对应的 <code>trap number</code> 。</p>
<p>Darwin XNU 源代码的便捷镜像由 GitHub 来托管：https://github.com/apple/darwin-xnu</p>
<p>这些 <code>trap number</code> 可以被搜索到。我们从<code>osfmk/x86_64/idt_table.h</code> 可以找到 <code>Trap Number:14</code> 表明看了一个页面错误。这个错误代码是一个位向量，用于描述在 mach 上的错误代码。<span class="citation" data-cites="macherror">(“Making Sense of I/O Kit Error Codes” 2018)</span></p>
<h3 id="macos-崩溃报告-binary-images-部分">macOS 崩溃报告 Binary Images 部分</h3>
<p>接下来，是崩溃应用程序所加载的 Binary Images。</p>
<p>以下是崩溃报告中前几个二进制文件的示例，为了便于演示，进行了截断：</p>
<pre><code>Binary Images:
       0x100238000 -        0x1ß00246fff
         com.apple.SiriNCService (146.4.5.1 - 146.4.5.1)
          &lt;5730AE18-4DF0-3D47-B4F7-EAA84456A9F7&gt;
           /System/Library/CoreServices/Siri.app/Contents/
           XPCServices/SiriNCService.xpc/Contents/MacOS/
           SiriNCService

       0x101106000 -        0x10110affb
         com.apple.audio.AppleHDAHALPlugIn (281.52 - 281.52)
          &lt;23C7DDE6-A44B-3BE4-B47C-EB3045B267D9&gt;
           /System/Library/Extensions/AppleHDA.kext/Contents/
           PlugIns/AppleHDAHALPlugIn.bundle/Contents/MacOS/
           AppleHDAHALPlugIn</code></pre>
<p>当二进制旁边出现 <code>+</code> 号时，则意味着它是操作系统的一部分。但是，我们看到某些第三方二进制旁边出现 <code>+</code> 号而系统二进制文件旁没有出现 <code>+</code> 号的示例，因为 <code>+</code> 并不是可靠的指示符（在 OS X 10.13.6上进行了最后测试）。</p>
<h3 id="macos-崩溃报告修改摘要">macOS 崩溃报告修改摘要</h3>
<p>接下来，这一节描述了崩溃过程中的所有外部修改：</p>
<pre><code>External Modification Summary:
  Calls made by other processes targeting this process:
    task_for_pid: 184
    thread_create: 0
    thread_set_state: 0
  Calls made by this process:
    task_for_pid: 0
    thread_create: 0
    thread_set_state: 0
  Calls made by all processes on this machine:
    task_for_pid: 72970
    thread_create: 0
    thread_set_state: 0</code></pre>
<p>macOS 是比 iOS 更开放的平台。这就允许在某些条件下运行过程中发生了修改。我们需要知道是否发生了这种事情，因为它可以使代码中的任何设计假设无效，由于可以在过程中修改寄存器，这就有可能导致项目崩溃。</p>
<p>通常我们可以看到如上的快照。值得注意的是，在所有情况下，<code>thread_set_state</code> 值均为 0。这意味着并没有任何进程直接连接到该进程来更改寄存器状态。这种操作对于托管运行时或调试器的实现是可接受的。在这些情况之外的操作会显得奇怪，需要进一步调查。</p>
<p>在下面的示例中，我们看到线程状态除了200个 <code>task_for_pid</code> 调用之外，还一次被外部进程更改了。</p>
<pre><code>External Modification Summary:
  Calls made by other processes targeting this process:
    task_for_pid: 201
    thread_create: 0
    thread_set_state: 1
  Calls made by this process:
    task_for_pid: 0
    thread_create: 0
    thread_set_state: 0
  Calls made by all processes on this machine:
    task_for_pid: 6184
    thread_create: 0
    thread_set_state: 1</code></pre>
<p>这些数据通常会让我们对应用程序在崩溃之前运行的环境产生怀疑。</p>
<p>通常只有第一等的程序（Apple 提供的）才有权限执行上述修改。我们可以安装执行这个操作的软件。</p>
<p>执行访问进程修改的 API 有如下要求：</p>
<ul>
<li>需要禁用系统完整性保护</li>
<li>进行修改的过程必须以 root 身份运行</li>
<li>进行修改的程序必须经过代码签名</li>
<li>程序必须被分配这些权利，将 <code>SecTaskAccess</code> 设置为 <code>allowed</code> 和 <code>debug</code>。</li>
<li>用户必须同意在其安全设置中信任该程序</li>
</ul>
<p>示例代码 <code>tfpexample</code> 演示了这一点。 <span class="citation" data-cites="icdabgithub">(“IOS Crash Dump Analysis Book Github Resources” 2018)</span></p>
<h3 id="macos-崩溃报告虚拟内存部分">macOS 崩溃报告虚拟内存部分</h3>
<p>崩溃报告接下来是虚拟内存摘要和区域类型细分。如果我们有一个用来渲染文档页面的图形丰富的应用程序，则可以查看，例如 CoreUI 的内存消耗。尽当我们用 Xcode Instruments 中的 memory profiler 工具分析该应用程序时，虚拟内存统计信息才有意义，因为这样我们就可以了解应用程序中内存的动态使用情况，从来在错误发生时发现错误。</p>
<p>这是报告的虚拟内存部分的示例：</p>
<pre><code>VM Region Summary:
ReadOnly portion of Libraries: Total=544.2M resident=0K(0%)
swapped_out_or_unallocated=544.2M(100%)
Writable regions: Total=157.9M written=0K(0%) resident=0K(0%)
swapped_out=0K(0%) unallocated=157.9M(100%)

                                VIRTUAL   REGION
REGION TYPE                        SIZE    COUNT (non-coalesced)
===========                     =======  =======
Accelerate framework               128K        2
Activity Tracing                   256K        2
CoreAnimation                      700K       16
CoreGraphics                         8K        2
CoreImage                           20K        4
CoreServices                      11.9M        3
CoreUI image data                  764K        6
CoreUI image file                  364K        8
Foundation                          24K        3
IOKit                             7940K        2
Image IO                           144K        2
Kernel Alloc Once                    8K        2
MALLOC                           133.1M       36
MALLOC guard page                   48K       13
Memory Tag 242                      12K        2
Memory Tag 251                      16K        2
OpenGL GLSL                        256K        4
SQLite page cache                   64K        2
STACK GUARD                       56.0M        6
Stack                             10.0M        8
VM_ALLOCATE                        640K        8
__DATA                            58.3M      514
__FONT_DATA                          4K        2
__GLSLBUILTINS                    2588K        2
__LINKEDIT                       194.0M       26
__TEXT                           350.2M      516
__UNICODE                          560K        2
mapped file                       78.2M       29
shared memory                     2824K       11
===========                     =======  =======
TOTAL                            908.7M     1206</code></pre>
<h3 id="macos崩溃报告系统配置文件部分">macOS崩溃报告系统配置文件部分</h3>
<p>崩溃报告的下一部分是相关硬件的摘要：</p>
<pre><code>System Profile:
Network Service: Wi-Fi, AirPort, en1
Thunderbolt Bus: iMac, Apple Inc., 26.1
Boot Volume File System Type: apfs
Memory Module: BANK 0/DIMM0, 8 GB, DDR3, 1600 MHz, 0x802C,
 0x31364B544631473634485A2D314736453220
Memory Module: BANK 1/DIMM0, 8 GB, DDR3, 1600 MHz, 0x802C,
 0x31364B544631473634485A2D314736453220
USB Device: USB 3.0 Bus
USB Device: BRCM20702 Hub
USB Device: Bluetooth USB Host Controller
USB Device: FaceTime HD Camera (Built-in)
USB Device: iPod
USB Device: USB Keyboard
Serial ATA Device: APPLE SSD SM0512F, 500.28 GB
Model: iMac15,1, BootROM IM151.0217.B00, 4 processors,
 Intel Core i5, 3.5 GHz, 16 GB, SMC 2.22f16
Graphics: AMD Radeon R9 M290X, AMD Radeon R9 M290X, PCIe
AirPort: spairport_wireless_card_type_airport_extreme
 (0x14E4, 0x142), Broadcom BCM43xx 1.0 (7.77.37.31.1a9)
Bluetooth: Version 6.0.6f2, 3 services, 27 devices,
 1 incoming serial ports</code></pre>
<p>有时，我们的应用程序与硬件设备进行紧密交互，如果通过基于标准的设备接口（例如 USB 接口）进行交互，则有可能会发生很多变化。考虑一下磁盘驱动。许多供应商提供磁盘驱动器，它们可能直接或独立运行。它们可以直接连接，或通过 USB 电缆连接，也可以通过 USB 集线器连接。</p>
<p>有时新的硬件，例如新型 MacBook Pro 会出现自己的硬件问题，因此可以看到与我们的应用程序无关的崩溃。</p>
<p>判断是否是硬件环境导致崩溃的关键是查看大量的崩溃报告以寻找某种规律。</p>
<p>作为应用程序开发人员，我们只会看到应用程序中的崩溃。但如果我们与提供崩溃的用户联系，我们可以询问是否有其他应用程序崩溃，或者是否存在任何系统稳定性问题。</p>
<p>另一个有趣的方面是，并非所有硬件都始终被系统主动使用。例如，当 MacBook Pro 连接到外部显示器时，将使用不同的图形RAM，并使用不同的图形卡（外部GPU与内部GPU）。如果我们的应用程序执行特殊操作，则在连接到外部显示器时，故障可能出在硬件而非我们的代码中，原因是它触发了硬件中的潜在故障。</p>
<p>运行系统诊断程序并查看仅针对特定的匿名 UUID 故障报告是否出现问题，是尝试并了解应用程序是否存在特定的计算机硬件问题的方法。 # 分析性故障排除</p>
<p>本章将讨论一个解决问题的正式技巧。这个技巧是提供一个询问正确问题的框架。</p>
<p>有一个著名的俗语告诫我们不要过渡狂热：</p>
<blockquote>
<p>“不要用大锤去砸螺母。”</p>
</blockquote>
<p>大多数问题都有直接而明显的解决方法。作为工程师、开发人员和测试人员，我们对解决问题都非常熟悉。然而，本章并不涉及那些类型的问题。</p>
<p>还有另外一个不太知名的俗语：</p>
<blockquote>
<p>“当您拿着锤子时，一切看起来都像钉子。”</p>
</blockquote>
<p>锤子最适合钉钉子，通常也可以砸东西，但是对其他的可能并不有用。锤子是对某种问题的解决方案。此外，我们思考问题的方式可以由一些技巧组成。如果我们增加可用的技巧，开始以不同的方式去思考问题，其中可能有一种方式会让我们获得我们想要的答案。</p>
<p>如果我们的工具箱中有一把扳手和一把钢锯。我们想拆除一个已生锈螺栓固定的旧浴室配件。由于螺栓无法转动，因此可能无法使用扳手。但是，使用钢锯锯掉螺栓头可能是一个可行的次佳解决方案。 通过观察有经验的水管工人或技工，就会发现这种技巧。</p>
<p>在这种情况下，我们将介绍“分析故障排除”。<span class="citation" data-cites="kepnertregoe">(“Analytic Troubleshooting” 2018)</span> 当我们用尽所有方法并且已经尝试了一些常见手段之后，本章将帮助我们推进解决问题。</p>
<p>这个方法是 <a href="https://www.kepner-tregoe.com/">Kepner Tregoe 方法</a> 的简化版本。</p>
<h2 id="优先考虑自己的问题">优先考虑自己的问题</h2>
<p>如果我们是一个应用程序（可能只有很少的用户）的唯一开发者，当我们收到崩溃报告时，可能会感到我们正在面临着一场好奇的智力挑战。</p>
<p>在专业的软件工程环境中，实际情况截然不同。通常会有一个团队进行统筹处理，对于不同客户进行优先级筛选，并且针对不同的产品和产品变形，来自不同客户的崩溃报告也很多。</p>
<p>我们必须确定要处理的崩溃的优先级。我们将从三个方法来思考问题：严重性、紧迫性和成长性。</p>
<h3 id="根据影响确定优先级">根据影响确定优先级</h3>
<p>对许多开发团队来说，崩溃是最重要的“P1”级别的错误，因为客户无法再使用应用程序做任何事情。为了要判断问题的严重性，我们首先要评估问题的 <strong>影响</strong>。问题发生时，用户正在做些什么？</p>
<p>如果客户正在进行商品购买，那么如果不能解决问题，显然我们的收入将受到影响。</p>
<p>如果是在更新我们的隐私设置时发生了崩溃，则表明存在对应设置的隐私问题。根据我们产品的具体类型，这可能是一个主要问题。</p>
<p>在我们的应用中内置分析工具，是评估影响的一种方法。然后可以在崩溃的同时分析这些步骤，更广泛的说是客户用例。然后，可以将最多最广泛的崩溃用例确定为最需要修复的崩溃问题。第三方崩溃报告集成服务的一项优势就是它们允许记录日志并与崩溃报告一起发送到崩溃报告服务器。</p>
<p>以下是日志可以记录的一些好的点：</p>
<ul>
<li>任何生命周期发生的时间，前台、后台、页面出现、页面消失</li>
<li>按钮点击</li>
<li>跳转</li>
<li>通知</li>
<li>弹出提醒</li>
<li>调用诸如照片选择器之类的辅助组件</li>
</ul>
<h3 id="根据截止日期确定优先级">根据截止日期确定优先级</h3>
<p>为了判断错误修复的紧迫性，我们需要评估与该错误相关的 <strong>截止日期</strong>。每当 Apple 更新产品线时（例如，每年 9月 iPhone 都会迭代新机型），市面上的应用程序都会进入一个自然而然的迭代期。新的用户将会去 App Store 下载新的应用。媒体上将会有很多关于 Apple 产品功能的讨论。因此，它被标记成一个良好的市场窗口。此时，任何会导致应用商店审核失败或应用首次使用的崩溃问题都变得越来越重要。Apple 有时会引入一个新的应用类别，例如手表应用或贴纸包。在第一天就可以使用，就会具有先发优势，甚至有可能成为 Apple 发布活动的一部分。</p>
<h3 id="根据趋势确定优先级">根据趋势确定优先级</h3>
<p>当我们看到崩溃报告数量的增长令人震惊，需要通过分析 <strong>趋势</strong> 来进行评估。我们可以查看随着时间的推移获得的崩溃报告数量，并查看是否存在峰值或上升趋势。</p>
<p>如果我们的应用程序由于 iOS 新版本中的功能而崩溃，那么最早遇到该问题的人就是更新了 iOS Beta 版的用户。之后，iOS 设备就会开始自动升级。有事，iOS 新版本将会以地理位置分段交错更新。我们希望这会在崩溃报告中所看到的趋势中得到反映。</p>
<p>如果我们在崩溃报告中看到一个峰值（先升后降），那么可能还有其他影响系统架构组件的因素。 例如，如果我们的应用程序依赖于后端服务器，该后端服务器为我们的应用程序以一种有问题的方式更新，则在修复服务器之前，我们可能会看到崩溃。</p>
<p>有时问题的时机可能很尴尬。例如，当要处理例如证书之类的安全凭证时，最好不要将其到期日期设置在传统假期中，例如圣诞节或农历新年，因为当它们到期时，可能没有人力来更新或者替换。</p>
<p>在热门假期之间发布重要的软件更新是极其不明智的做法。如果我们的产品需要来迎合假期间各种热点，那么就需要人力来处理相应的问题。</p>
<p>密切关注趋势，在崩溃更广泛的传播之前，我们能够得以安排工作并解决问题。不同的应用程序具有不同的风险状况。例如，对移动设备管理 API 敏感的应用程序应使用 iOS Beta 版本进行测试，因为对系统级别来说，细微的更改可能会产生巨大的影响，所以需要今早的进行测试。如果我们有一个对绘制敏感的应用程序，那么我们则应该关注新的硬件设备、硬件规格的更新，挺尸我们应该有一个整体的测试框架，该框架可以测试我们所依赖平台中的关键 API，因此可以对新的 OS 版本或硬件平台进行快速评估。</p>
<p>崩溃报告趋势不一定是不利的。如果仅在交旧的硬件版本上看到异常崩溃，呢么我们预计趋势会随着时间的推移而下降，因此有可能取消对此类崩溃问题的优先级。</p>
<h2 id="说明问题">说明问题</h2>
<p>我们提供的崩溃信息有：崩溃报告，客户日志，分析数据等。应该总结为 OBJECT / DEFECT 风格的简短问题陈述。这通常是分担潜在大量崩溃报告的关键的第一步。这使得我们对问题有了第一手的资料，并使管理人员和其他有关方面可以了解我们在产品质量，成熟度，风险等方面的状况。</p>
<p>首先，我们陈述问题的对象。 那就是应用程序或产品失败了。 然后，我们指出缺陷。 这就是我们看到的“不良行为”。 它应该像“使用 Apple Share 按钮时 CameraApp Lite 发生崩溃”那样简单。 并且应该在错误管理系统中跟踪该问题。</p>
<h2 id="指出问题">指出问题</h2>
<p>在分析故障排除方法中，指定问题是最重要的一步，因为我们在这里看到了我们认知上盲区，这就提示了问题，这些问题会引导我们找到解决方案。</p>
<p>我们写出一个有四行两列的表格，如下所示：</p>
<table>
<thead>
<tr class="header">
<th>Item</th>
<th>IS</th>
<th>IS NOT</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>WHAT</td>
<td>Seen</td>
<td>Not Seen</td>
</tr>
<tr class="even">
<td>WHERE</td>
<td>Seen</td>
<td>Not Seen</td>
</tr>
<tr class="odd">
<td>WHEN</td>
<td>Seen</td>
<td>Not Seen</td>
</tr>
<tr class="even">
<td>EXTENT</td>
<td>Seen</td>
<td>Not Seen</td>
</tr>
</tbody>
</table>
<p>对一个团队来说，分析性故障排除很有效果。通过一些领域专业人员（专家），以及来自其他领域的人员和非技术人员，我们可以组建一个优秀的故障排除团队。专家有时会忽略问基本的问题，而不太了解情况的员工可以问清楚问题，从而进一步露出问题中的隐藏假设。棘手的客户问题让人干到焦虑，所以让团队聚在一起解决问题可以缓解紧张气氛，提高士气。有时可以邀请我们的客户参加； 这通常可以加快过程，甚至提出更多的假设。</p>
<p>当作为一个团队进行故障排除时，我们可以使用一块白板，将其分成一个表格，如上所述。每个人都可以得到一份讲义，列出表格中每条每列要问的问题。</p>
<p>与本书相关的网站上有用于分析故障排除的辅助材料和讲义。<span class="citation" data-cites="icdabgithub">(“IOS Crash Dump Analysis Book Github Resources” 2018)</span></p>
<p>当我们自己进行故障排除时，列出所有问题并将它们填入表格是一种很好的方法。远离电脑（或代码层面），列举出要检查的项目清单是很好的，因为这样可以消除深入研究细节的直接冲动。 相反，一旦有了要跟踪的项目清单，就可以确定工作的优先级。</p>
<p>我们首先在 IS 列中填写详细信息。然后，我们填写 IS NOT 列。通常，我们会注意到网格中没有数据的空白区域。这是一个信号，让我们去收集更多的数据或做更多的研究。这样做的目的是使 IS 和IS NOT 列之间的相关_差异_尽可能小。这使我们能够得到一个可以测试的好假设，或者是可以优先被考虑测试的一些假设。</p>
<p>任何可能的问题解决方案都必须 <strong>完全</strong> 解释问题规范中的 IS 和 IS NOT 部分。通常，我们想到的第一个解决方案只能解释了问题规范中的部分问题。多花一点时间思考潜在的原因，或者多做一点研究，都是很好的时间投资，尤其是当尝试不同的备选解决方案很困难或者很耗时的时候。</p>
<p>我们将了解系统规范及其在这些约束下运行时的行为。事实上，随着新软件和硬件的发布，系统会随着时间的推移而发展。因此，我们必须重复处理主要的信息源，并进行实验来完善这种理解。这使我们能够发现良好的问题，并使我们能够提出相关假设。在提出问题，了解我们的系统，然后发现新的相关问题之间通常有一个积极的反馈循环。</p>
<h3 id="如何提问">如何提问</h3>
<ul>
<li>WHAT IS
<ul>
<li>什么出现了问题？</li>
<li>出现了怎样的问题？</li>
</ul></li>
<li>WHAT IS NOT
<ul>
<li>哪些（版本、设备、情况等）可能出现问题但是没有？</li>
<li>哪些可能的故障并没有一并出来？</li>
</ul></li>
<li>WHERE IS
<ul>
<li>当发现问题时，其地理位置在哪里？</li>
<li>问题出在哪里？</li>
</ul></li>
<li>WHERE IS NOT
<ul>
<li>当我们本该看到问题却没有看到，那么问题在哪里？</li>
<li>问题可能出现在什么地方，但是并没有</li>
</ul></li>
<li>WHEN IS
<ul>
<li>第一次发现问题是什么时候？</li>
<li>什么时候问题又出现了？</li>
<li>时间上有什么规律吗？</li>
<li>在事物的生命周期中某个时候才首次发现问题？</li>
</ul></li>
<li>WHEN IS NOT
<ul>
<li>什么时候应该可以发现问题，但并没有？</li>
<li>什么时候问题应该复现，但并没有？</li>
<li>在事物的生命周期中的某个时候应该可以看到问题，但并没有？</li>
</ul></li>
<li>EXTENT IS
<ul>
<li>这样问题到底有多少？</li>
<li>缺陷的程度如何？</li>
<li>这个问题有哪些缺陷？</li>
<li>发展的趋势是怎样？</li>
</ul></li>
<li>EXTENT IS NOT
<ul>
<li>有哪些情况可能也有这个问题，但实际上没有？</li>
<li>问题可能严重到什么程度，但实际上不是？</li>
<li>可能存在多少缺陷，但实际上没有？</li>
<li>问题的趋势应该是怎样，但实际上不是？</li>
</ul></li>
</ul>
<h2 id="问题范例">问题范例</h2>
<p>首先，问题说明问题似乎并不常见，而且措辞笨拙。 查看一些实际示例有助于更清楚地解释事情。 这里将使用不同的假设示例来专注于特定问题，但是为了简洁起见，我们不会在任何给定示例上列举全部问题。</p>
<h3 id="cameraapp-what-is-is-not-示例">CameraApp What Is / Is Not 示例</h3>
<p>思考一下这个问题：“客户按下 Apple 共享按钮时，相机应用程序崩溃”</p>
<ul>
<li>WHAT IS
<ul>
<li>What things have a problem?
<ul>
<li>iOS 10.1、10.2、10.3上的 1.4.5 版本的 CameraApp。</li>
<li>在主线程中</li>
<li>方法 isAllowedToShare()</li>
<li>Apple 的共享按钮</li>
</ul></li>
<li>What is wrong with them?
<ul>
<li>共享按钮导致应用崩溃。</li>
</ul></li>
</ul></li>
<li>WHAT IS NOT
<ul>
<li>What things could have a problem but don’t?
<ul>
<li>iOS 9.3.5 上的 1.4.4 版本的 CameraApp。</li>
<li>后台线程从来没发生崩溃。</li>
<li>其他功能和拍照功能正常。</li>
<li>拍照按钮正常工作。</li>
</ul></li>
<li>What could be wrong but is not?
<ul>
<li>其他按钮可能会导致崩溃，但可以正常工作。</li>
<li>我们没有看到任何系统弹出错误。</li>
</ul></li>
</ul></li>
</ul>
<p>为了取得进展，我们需要配合并收紧IS和IS NOT答案。 我们首先看一下 IS NOT 部分，因为它通常是表格的一面，相当空泛，需要一些思想和启发才能得出与 IS 部分相关的额外 IS NOT 答案。</p>
<p>显而易见的事情是，应用程序版本 1.4.4 是否可以在 iOS 10.x上运行。</p>
<p>iOS 10.x是iOS 9.3.5的主要更新，因此其规范和对应用程序的要求将有所不同。 因此，接下来要看的是Apple文档中的 What’s New 部分，从更高的角度查看iOS 10.x over 9.x的新增功能。 这将促使我们提出更为清晰的问题。</p>
<p>如果 10.x 要求应用程序具有某些 <code>Info.plist</code> 设置，则可以在上方的表格中并没有解释相机应用程序中的任何 <code>Info.plist</code> 差异，以及与其他已知可用的应用程序的 <code>Info.plist</code> 在执行共享的 iOS 10.x 和 9.x上的差异。</p>
<p>在此示例中，共享按钮是有问题的。 我们可以获得使用共享按钮的一些示例代码，并查看它是否在与我们的问题类似的环境中崩溃。 我们可以在独立的应用程序中测试代码，也可以将其移植到我们的 Camera App 中以查看其是否可以正常工作。</p>
<p>在此示例中，我们只说系统并未弹出窗口。那么控制台消息如何？ 我们可能会发现系统告诉我们系统崩溃我们的应用程序的原因。</p>
<p>候选解决方案将是 “iOS 10.x需要不同的 <code>Info.plist</code> 设置才能正常工作，否则如我们所见，系统将指定使我们的应用程序崩溃。”</p>
<h3 id="imac-where-is-is-not-示例">iMac Where Is / Is Not 示例</h3>
<p>思考一下这个问题：“ iMac 经常崩溃，需要不断地进行硬件维修。”</p>
<ul>
<li>WHERE IS
<ul>
<li>When the problem was noticed, where was it geographically?
<ul>
<li>放置在一楼转角处靠窗的的一台 Apple iMac。</li>
</ul></li>
<li>Where is the problem on the thing?
<ul>
<li>问题出在电源，屏幕，主系统板和内存芯片上的电气故障（多次出现问题）。</li>
</ul></li>
</ul></li>
<li>WHERE IS NOT
<ul>
<li>Where could the thing be when we should have seen the problem but did not?
<ul>
<li>相同的 Apple iMac 放在地下室，在 IT 部门登台区域以及在 Apple 工厂进行测试时都很好。</li>
</ul></li>
<li>Where could the problem be on the thing but isn’t?
<ul>
<li>问题可能出在软件中，但不是。</li>
<li>问题可能出在USB外设上，但不是。</li>
<li>问题可能是打印机，台灯，照明灯或空调出现电气故障，但不是。</li>
<li>问题可能出在办公桌抽屉中的笔记本电脑上了，但事实并非如此。</li>
</ul></li>
</ul></li>
</ul>
<p>在此示例中， IS NOT 列中有许多项。立刻让我们感觉像是可以因此考虑好的假设。 与此相反，在前面的示例中，WHAT IS NOT 部分中，我们在提出假设之前必须进行大量研究。</p>
<p>我们注意到只有iMac有问题，而打印机没有问题。那么如果我们交换打印机和iMac的位置，因为它们都是敏感的电子产品，我们可以在 IS 和 IS NOT 之间获得很好的对比。</p>
<p>电子设备只能在特定的特定环境条件下运行。 需要正确的电压，电流，温度，湿度，有限的电磁干扰等。 如果我们在进行现场调查时考虑到此类需求规范，则可以发现造成此特定位置问题的原因。 我们也可以尝试使用电涌保护器，也可以不使用电涌保护器，因为众所周知，电源尖峰会损坏电子设备。</p>
<h3 id="数据库应用-when-is-is-not-示例">数据库应用 When Is / Is Not 示例</h3>
<p>思考一下这个问题：”应用审查期间一个数据库应用崩溃“</p>
<ul>
<li>WHEN IS
<ul>
<li>When was the problem first noticed?
<ul>
<li>我们第一次发现此问题 App Store 应用审核期间。</li>
</ul></li>
<li>When has the problem been seen again?
<ul>
<li>第二次是我们将该应用提交审核。</li>
</ul></li>
<li>Is there any pattern in the timing?
<ul>
<li>启动应用后，问题发生的时间相同。</li>
</ul></li>
<li>When in the lifecycle of the thing was the problem first noticed?
<ul>
<li>问题始终在应用启动过程中。</li>
</ul></li>
</ul></li>
<li>WHEN IS NOT
<ul>
<li>When could the problem have been noticed but wasn’t?
<ul>
<li>开发人员在开发环境中从未见过该问题。</li>
</ul></li>
<li>When could it have been seen again but wasn’t?
<ul>
<li>随后启动该应用程序也运行的很好。</li>
</ul></li>
<li>When else in the lifecycle of the thing could the problem be seen but wasn’t?
<ul>
<li>在应用程序中按更新按钮或更改目标数据库连接字符串时，未发现问题。</li>
</ul></li>
</ul></li>
</ul>
<p>显然，这是一个应用程序启动问题。 这个例子强调了有时候一个领域的问题会触发另一个领域的问题和研究。 对于 WHAT IS / IS NOT 部分来说，环境的干净程度以及启动环境的配置状态是显而易见的问题。</p>
<p>在 WHEN is NOT 部分发现了一条线索。可以设置和重新配置数据库连接字符串。可能是一个空连接字符串，或者缺少设置，或者没有触发第一次使用的设置代码。也许调试版本的代码会跳过第一次使用的工作流来加速功能开发，但是这些功能在用于应用商店审查的应用程序的发布部署中并不存在。</p>
<h3 id="游戏应用-extent-is-is-not-示例">游戏应用 Extent Is / Is Not 示例</h3>
<p>思考一下这个问题：”在游戏的不同级别（关卡或者等级），AlienGame 应用都会出现性能问题/崩溃“</p>
<ul>
<li>EXTENT IS
<ul>
<li>How many things have the problem?
<ul>
<li>500个安装了该应用不同设备，总共有2000个。</li>
</ul></li>
<li>What is the extent of the defect?
<ul>
<li>有时很严重； 崩溃了。有时很温和；掉帧了。</li>
<li>有时帧率始终保持良好状态。</li>
</ul></li>
<li>How many defects are on the thing?
<ul>
<li>5 种不同类型的游戏渲染线程最终会崩溃（在不同情况下）。</li>
</ul></li>
<li>What is the trend?
<ul>
<li>随着我们的安装量的增长，崩溃数量略有下降的趋势。</li>
</ul></li>
</ul></li>
<li>EXTENT IS NOT
<ul>
<li>How many things could have the problem but don’t?
<ul>
<li>所有已安装或未安装的设备都有这个问题，但是目前我们看到的是25%。</li>
</ul></li>
<li>What could be the extent of the problem but isn’t?
<ul>
<li>我们从来没有看到帧率下降然后提高。</li>
<li>我们从来没有见过好的安装程序能够解决崩溃问题或掉帧问题。</li>
</ul></li>
<li>How many defects could be present but aren’t?
<ul>
<li>我们从没看过主线程崩溃。</li>
<li>在 6 种类型的渲染线程中，有一种是特殊的，因为它从未在崩溃或帧率下降过程中出现过。</li>
</ul></li>
<li>What could the trend be but isn’t?
<ul>
<li>趋势可能由崩溃变得越来越普遍（超过25％），但没有。</li>
<li>趋势可能是崩溃仅在某些日子发生，但事实并非如此。</li>
</ul></li>
</ul></li>
</ul>
<p>这个例子很难理解。 我们需要了解应用程序的架构才能提出好的问题。 一些线索出现了。 渲染线程有6种类型，其中一种很好。 另外，主线程很好。 我们需要探索线程之间的相关差异。</p>
<p>当我们遇到的问题并不总是发生时，一种策略是考虑可以采取什么措施使问题变得更糟，从而更频繁地发生。 然后，当我们有一个候选解决方案时，我们可以设置一个置信度阈值（没有看到失败的迭代次数），并在使问题更有可能出现的特殊环境中针对这个阈值测试修复。</p>
<p>另一个线索是 25％ 的用户设备有问题。 如果问题是由于使用不同的硬件而导致的，因此硬件功能各不相同，那么我们可以看到大约有25％ 的用户使用 iPad 而不是 iPhone。 但是，严格说来这 25％ 的问题并没有明确告诉我们，环境中的其他因素可能会影响应用程序的行为。也许在安装过程中，服务器是在托管游戏后端的四台服务器中以循环方式选择的。 此外，在开发过程中，使用的服务器也许是与我们的客户使用的生产服务器不同的特殊开发服务器。 同样，IS NOT 部分提供了有关在哪里寻找潜在解决方案的最有启发性的线索。</p>
<p>如果我们不进行分析性故障排除，则在此示例中，第一个本能将是检查内存泄漏，内存压力，硬件限制等。这种分析很容易会花费一周的工程时间。 虽然此类问题有可能导致丢帧而无法完全解释我们所看到的缺陷模式； 他们不会解释为什么恰好有25％的用户遇到了这个问题。</p>
<h2 id="macbook-pro-t2-的问题">2018 MacBook Pro T2 的问题</h2>
<p>本节描述了 2018 款 MacBook Pro 电脑崩溃的问题 叙述来自受影响用户的讨论组发布 <span class="citation" data-cites="macbookproT2">(“2018 Macbook Pros Bridge Os Error” 2018)</span> 和媒体报道 <span class="citation" data-cites="appleinsiderimacpro">(“Apple’s T2 Chip May Be Behind iMac Pro, Macbook Pro Crashes” 2018)</span></p>
<p><strong>问题描述：</strong> 2018 MacBook Pro 计算机在休眠期间因 Bridge OS 错误而崩溃。</p>
<ul>
<li>WHAT IS
<ul>
<li>What things have a problem?
<ul>
<li>MacBook Pro Mid 2018 (13-inch, 15-inch)</li>
<li>iMac Pro</li>
<li>iBridge2,1</li>
<li>iBridge2,3</li>
<li>连接了 USB 外接设备的配置实例</li>
<li>没有外接 USB 设备的配置实例，从休眠中唤醒</li>
<li>没有外接USB 设备而是连接电源适配器的配置实例，从休眠中唤醒</li>
<li>具有旧版内核扩展的配置实例（xbox控制器）</li>
<li>删除了xboxcontroller的配置实例（较少崩溃）</li>
<li>macOS High Sierra 10.13.6，带有补充更新，来自抹除磁盘和全新安装</li>
<li>休眠期间发生崩溃</li>
<li><code>panic: ANS2 Recoverable Panic - assert failed</code></li>
<li><code>panic: macOS watchdog detected</code></li>
<li><code>panic: x86 global reset detected</code></li>
<li><code>panic: x86 CPU CATERR detected</code></li>
<li>具有 DiskUtility-&gt;First Aid crypto_val 错误的实例</li>
<li>具有抹除磁盘以消除 crypto_val 错误的实例</li>
<li>使用相同的客户配置但是完全替换了 MacBook Pro 硬件</li>
<li>在 2018 MacBook Pro 中禁用了 Power Nap</li>
<li>不触摸触控栏仍存在休眠/唤醒问题</li>
</ul></li>
<li>What is wrong with them?
<ul>
<li>系统在Bridge OS 出问题后重新启动</li>
<li>电脑发烫</li>
<li>磁盘检查失败</li>
<li>外接设备被虚假唤醒</li>
</ul></li>
</ul></li>
<li>WHAT IS NOT
<ul>
<li>What things could have a problem but don’t?
<ul>
<li>MacBook Pro Mid 2017 models or older MacBook Pros</li>
<li>iBridge1,1</li>
<li>MacBook Pro booted in Safe Mode</li>
<li>iPad, iPhone, Apple Watch</li>
</ul></li>
<li>What could be wrong but is not?
<ul>
<li>在电脑运行时从未出现问题</li>
<li>在引导过程中从来没有问题</li>
<li>当用户关闭系统后，从未出现问题</li>
</ul></li>
</ul></li>
<li>WHERE IS
<ul>
<li>When the problem was noticed, where was it geographically?
<ul>
<li>在客户处</li>
<li>在书桌上和手提电脑包中</li>
</ul></li>
<li>Where is the problem on the thing?
<ul>
<li>在T2芯片（源自watchOS）中的BridgeOS操作系统软件</li>
</ul></li>
</ul></li>
<li>WHERE IS NOT
<ul>
<li>Where could the thing be when we should have seen the problem but did not?
<ul>
<li>Apple 硬件验证工具（大概-只有Apple知道）</li>
</ul></li>
<li>Where could the problem be on the thing but isn’t?
<ul>
<li>在主 CPU 或主板上</li>
<li>在引导加载程序上</li>
<li>在多媒体芯片和网络芯片上（它们都有自己的操作系统）</li>
</ul></li>
</ul></li>
<li>WHEN IS
<ul>
<li>When was the problem first noticed?
<ul>
<li>大约30分钟的休眠后，然后唤醒计算机</li>
</ul></li>
<li>When has the problem been seen again?
<ul>
<li>随机，大概一天一次或一周五次</li>
</ul></li>
<li>Is there any pattern in the timing?
<ul>
<li>至少在运行 30 分钟后，进入休眠状态</li>
</ul></li>
<li>When in the lifecycle of the thing was the problem first noticed?
<ul>
<li>客户购买并安装软件后</li>
<li>重新安装后</li>
<li>重新抹除磁盘并安装后</li>
</ul></li>
</ul></li>
<li>WHEN IS NOT
<ul>
<li>When could the problem have been noticed but wasn’t?
<ul>
<li>在 Apple 的组装和验证机构（大概-只有Apple知道）</li>
</ul></li>
<li>When could it have been seen again but wasn’t?
<ul>
<li>退回设备的退货部门（大概-只有Apple知道）</li>
</ul></li>
<li>When else in the lifecycle of the thing could the problem be seen but wasn’t?
<ul>
<li>主动使用计算机期间从未休眠</li>
</ul></li>
</ul></li>
<li>EXTENT IS
<ul>
<li>How many things have the problem?
<ul>
<li>通过间接测量，在 iMac Pro 服务退货中，在 103 个中有 4个发现此问题</li>
</ul></li>
<li>What is the extent of the defect?
<ul>
<li>内核问题，看起来是一种内核问题，可能有三种</li>
</ul></li>
<li>How many defects are on the thing?
<ul>
<li>单实例内核紧急故障</li>
</ul></li>
<li>What is the trend?
<ul>
<li>在未来的随机休眠期后再次重复，有时不使用外围设备时复现的频率较低</li>
</ul></li>
</ul></li>
<li>EXTENT IS NOT
<ul>
<li>How many things could have the problem but don’t?</li>
<li>有一个引导加载程序，主操作系统和其他电子部件，它们从未出现问题</li>
<li>What could be the extent of the problem but isn’t?</li>
<li>故障崩溃后可能会持续发生故障，但这没有看到。</li>
<li>每台机器可能会出现一次故障，但又会再次出现故障</li>
<li>How many defects could be present but aren’t?</li>
<li>可能是警告或信息性消息，但我们只发现故障</li>
<li>What could the trend be but isn’t?</li>
<li>每个客户的问题都是随机发生的，但从未增加或减少</li>
</ul></li>
</ul>
<h3 id="故障分析">故障分析</h3>
<p>以上信息类似于我们在困难问题中经常看到的信息。 WHAT IS 列中有很多数据。</p>
<p>首要的主要结论是问题必须出在 iMac Pro 和 MacBook Pro 中使用的较新的 T2 芯片。 缺陷的模式和实际的故障（在 T2 芯片上运行的Bridge OS 中）清楚地表明了这一点。</p>
<p>第二点是失败的数量很少。与 MacBook Pro 相比，iMac Pro 是小批量计算机，因此该问题很可能在 iMac Pro 生产过程中曾出现过，但这并不是由于它发生故障的可能性很小。</p>
<p>我们看到问题永远不会在启动，有序关闭或频繁使用期间发生。 这很有趣，因为在硬件验证期间，通常会对计算机进行压力测试以解决问题。 它们通常不会处于睡眠状态以查看它们是否仍执行唤醒功能。 因此，可能存在测试策略问题。</p>
<p>对于客户来说，替换硬件后仍然存在相同的问题。 这是一个有用的信息，因为它表明了缺陷的稳定性。 随着时间的流逝，Apple 将收集已知有问题的计算机，因此可以进行验证的有缺陷的计算机数量将大大改善。</p>
<p>上述数据集的主要缺陷是没有 <code>pmset</code>日志。 这是提供详细的睡眠/唤醒行为日志。</p>
<p>潜在的关键数据点是使用安全模式启动的客户从未发现此问题。 那么关于 Bridge OS 的行为，安全模式启动有什么特别之处吗？</p>
<p>看来30分钟是睡眠时间的关键指标。那么 30 分钟时可能会有一个阈值，也许要进入深度休眠而不是浅休眠。</p>
<p>理解该问题的一种策略是使其更频繁地发生。 例如，通过手段有可能使计算机非常快速地进入深度休眠状态。 这就会使问题在 30 秒后出现，而不是在 30 分钟的休眠后随机出现。</p>
<p>如果可以使问题更加频繁，则可以编写自动系统测试。 然后，对 Bridge OS 的任何修复都将具有强大的测试组件来对其进行验证。</p>
<p>我们没有 Bridge OS 的源代码。所以辨别所看到的三个崩溃之间的差异将很有趣。 例如，有时有一个案例陈述包含 20 种可能的故障，而仅输入一个。这揭示了有关问题规范中 WHERE IS NOT 的信息。</p>
<p>我们没有故障机器的寄存器信息。 当发生底层问题时，处理器文档将使系统架构师可以准确地查找故障的种类（超时，奇偶校验错误等）。在我们的问题规范中，我们需要更精确地确定问题所在 WHERE。 BridgeOS 可能只是一个金丝雀，告诉我们其他地方的问题。 一些客户已收到完整的硬件更换，但仍然看到问题。 则表明是软件问题。</p>
<p>英特尔已经描述了其架构的更新，该更新中可以发送 CATERR 信号而不是 IERR 或 MCERR。<span class="citation" data-cites="intelrob">(“Debugging Processor Reorder Buffer Timeout: Guide” 2018)</span> 因此，规范的更新可能意味着系统软件不再兼容，因此 Bridge OS 需要更新。</p>
<p>一种方法是遵循英特尔调试指南 <span class="citation" data-cites="intelrob">(“Debugging Processor Reorder Buffer Timeout: Guide” 2018)</span>。它有很多好的建议。 当BridgeOS遇到问题时，应进行更新寄存器以打印出相关的诊断。</p>
<h1 id="siri崩溃">Siri崩溃</h1>
<h2 id="我们为什么要关注-siri-崩溃">我们为什么要关注 Siri 崩溃？</h2>
<p>这是 Siri 在 Mac 上崩溃的示例。 请注意，Mac 上的二进制文件未加密。这意味着我们可以演示如何使用第三方工具来研究出错的二进制文件。但由于只有 Apple 拥有 Siri 的源代码，因此它增加了挑战难度，并迫使我们对问题进行抽象思考。</p>
<h2 id="崩溃报告-1">崩溃报告</h2>
<p>以下是崩溃报告，为了便于演示，将其适当截断：</p>
<pre><code>Process:               SiriNCService [1045]
Path:                  
/System/Library/CoreServices/Siri.app/Contents/
XPCServices/SiriNCService.xpc/Contents/MacOS/SiriNCService
Identifier:            com.apple.SiriNCService
Exception Type:        EXC_BAD_ACCESS (SIGSEGV)
Exception Codes:       KERN_INVALID_ADDRESS at 0x0000000000000018
Exception Note:        EXC_CORPSE_NOTIFY
VM Regions Near 0x18:
--&gt;
    __TEXT                 0000000100238000-0000000100247000
    [   60K] r-x/rwx SM=COW
    /System/Library/CoreServices/Siri.app/Contents/
    XPCServices/SiriNCService.xpc/Contents/MacOS/SiriNCService

Application Specific Information:
objc_msgSend() selector name: didUnlockScreen:

Thread 0 Crashed:: Dispatch queue: com.apple.main-thread
0   libobjc.A.dylib                 
0x00007fff69feae9d objc_msgSend + 29
1   com.apple.CoreFoundation        0x00007fff42e19f2c
 __CFNOTIFICATIONCENTER_IS_CALLING_OUT_TO_AN_OBSERVER__ + 12
2   com.apple.CoreFoundation        0x00007fff42e19eaf
___CFXRegistrationPost_block_invoke + 63
3   com.apple.CoreFoundation        0x00007fff42e228cc
 __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__ + 12
4   com.apple.CoreFoundation        0x00007fff42e052a3
__CFRunLoopDoBlocks + 275
5   com.apple.CoreFoundation        0x00007fff42e0492e
 __CFRunLoopRun + 1278
6   com.apple.CoreFoundation        0x00007fff42e041a3
CFRunLoopRunSpecific + 483
7   com.apple.HIToolbox             0x00007fff420ead96
RunCurrentEventLoopInMode + 286
8   com.apple.HIToolbox             0x00007fff420eab06
ReceiveNextEventCommon + 613
9   com.apple.HIToolbox             0x00007fff420ea884
_BlockUntilNextEventMatchingListInModeWithFilter + 64
10  com.apple.AppKit                
0x00007fff4039ca73 _DPSNextEvent + 2085
11  com.apple.AppKit                0x00007fff40b32e34
-[NSApplication(NSEvent) _nextEventMatchingEventMask:
untilDate:inMode:dequeue:] + 3044
12  com.apple.ViewBridge            0x00007fff67859df0
-[NSViewServiceApplication nextEventMatchingMask:untilDate:
inMode:dequeue:] + 92
13  com.apple.AppKit                0x00007fff40391885
-[NSApplication run] + 764
14  com.apple.AppKit                0x00007fff40360a72
NSApplicationMain + 804
15  libxpc.dylib                    
0x00007fff6af6cdc7 _xpc_objc_main + 580
16  libxpc.dylib                    
0x00007fff6af6ba1a xpc_main + 433
17  com.apple.ViewBridge            0x00007fff67859c15
-[NSXPCSharedListener resume] + 16
18  com.apple.ViewBridge            0x00007fff67857abe
NSViewServiceApplicationMain + 2903
19  com.apple.SiriNCService         
0x00000001002396e0 main + 180
20  libdyld.dylib                   
0x00007fff6ac12015 start + 1</code></pre>
<h2 id="崩溃详情">崩溃详情</h2>
<p>看着这个 09:52 发生的崩溃，我们可以看到</p>
<p><code>Exception Type:        EXC_BAD_ACCESS (SIGSEGV)</code></p>
<p>这意味着我们正在访问不存在的内存。 正在运行的程序（称为TEXT）是</p>
<pre><code>/System/Library/CoreServices/Siri.app/Contents/
XPCServices/SiriNCService.xpc/Contents/MacOS/SiriNCService</code></pre>
<p>这很有趣，因为通常是应用程序发生崩溃。 但在这里，我们看到一个软件组件发生了崩溃。 Siri服务是一个分布式应用程序，它使用跨进程通信（xpc）来完成其工作。 从上面对xpc的引用中我们可以看到。</p>
<p>我们在试图调用一个不再存在的对象的方法是什么？ crash dump 为我们提供了有用的答案：</p>
<p><code>Application Specific Information: objc_msgSend() selector name: didUnlockScreen:</code></p>
<p>现在我们必须对崩溃的三个方面 <em>what</em>, <em>where</em> 和 <em>when</em> 做出一个近似的解答。 在 <code>SiriNCService</code>中，当对一个不存在的对象调用<code>didUnlockScreen</code>时，Siri 的一个组件崩溃了。</p>
<h2 id="使用我们的工具">使用我们的工具</h2>
<p>为了更进一步了解，我们需要使用 <code>class-dump</code>工具。</p>
<p><code>class-dump SiriNCService &gt; SiriNCService.classdump.txt</code></p>
<p>查看输出的一部分，如下所示：</p>
<pre><code>@property __weak SiriNCService *service;
 // @synthesize service=_service;
- (void).cxx_destruct;
- (BOOL)isSiriListening;
- (void)_didUnlockScreen:(id)arg1;
- (void)_didLockScreen:(id)arg1;</code></pre>
<p>我们看到确实有一个方法 <code>didUnlockScreen</code>，并且我们看到有一个 <code>service</code> 对象，该对象被 <strong>弱</strong> 引用。 这意味着该对象未保留，可能会释放。 通常，这意味着我们只是 SiriNCService 的用户，而不是所有者。 我们并不持有对象的生命周期。</p>
<h2 id="软件工程见解">软件工程见解</h2>
<p>这里潜在的软件工程问题是生命周期问题。应用程序的一部分具有我们没有预料到的对象生命周期。作为健壮性和防御性编程的最佳实践，应该编写代码来检测服务的缺失。有可能发生的情况是，软件会随着时间的推移而得到维护，但随着新功能的添加，对象的生命周期会变得更加复杂，但使用对象的旧代码却没有同步更新。</p>
<p>再进一步，我们应该问这个组件使用了哪些弱属性？由此我们可以创建一些简单的单元测试用例，当这些对象为 nil 时，它们可以测试代码。然后我们可以回过头来，为代码路径添加健壮性，假设对象是非 nil 的。</p>
<p>进一步回顾一下，这个组件的设计中是否有什么不寻常的地方需要进行集成测试?</p>
<pre><code>grep -i heat SiriNCService.classdump.txt
@protocol SiriUXHeaterDelegate &lt;NSObject&gt;
- (void)heaterSuggestsPreheating:(SiriUXHeater *)arg1;
- (void)heaterSuggestsDefrosting:(SiriUXHeater *)arg1;
@interface SiriNCAlertViewController : NSViewController
&lt;SiriUXHeaterDelegate, AFUISiriViewControllerDataSource,
 AFUISiriViewControllerDelegate&gt;
    SiriUXHeater *_heater;
@property(readonly, nonatomic)
SiriUXHeater *heater; // @synthesize heater=_heater;
- (void)heaterSuggestsPreheating:(id)arg1;
- (void)heaterSuggestsDefrosting:(id)arg1;
@interface SiriUXHeater : NSObject
    id &lt;SiriUXHeaterDelegate&gt; _delegate;
@property(nonatomic)
__weak id &lt;SiriUXHeaterDelegate&gt; delegate;
 // @synthesize delegate=_delegate;
- (void)_suggestPreheat;</code></pre>
<p>该组件似乎可以准备就绪，并具有各种级别的初始化和取消初始化。也许这种复杂性是为了让用户界面具有响应性。但是它向我们传递了这样一个信息：这个组件需要一个集成测试套件，它可以对状态机进行编码，以便我们了解服务的生命周期</p>
<h2 id="经验教训-1">经验教训</h2>
<p>我们从使用 HOWTO 知识（了解崩溃报告）到使用工具来获得基本的知识水平。然后，我们开始应用软件工程经验，然后开始对组件的实际设计进行推理，以询问我们如何到达这里以及应该采取什么措施来避免该问题。在 crash dump 分析期间，从查看问题的伪像到了解需要做的事情的过程是一个常见的主题。而仅仅专注于理解崩溃报告的方法是无法实现的。为了真正取得进展，我们需要换个角度，从不同角度看待事物。</p>
<h1 id="运行时崩溃">运行时崩溃</h1>
<p>在本章中，我们将展示那些运行时检测到问题并导致应用崩溃的示例。</p>
<p>在崩溃报告中，我们可以通过异常类型 <code>EXC_BREAKPOINT (SIGTRAP)</code> 来区分这些崩溃。</p>
<p>我们考虑两个例子。 第一个例子，说明运行时如何处理强制展开nil可选项的情况。 我们的第二个例子说明运行时如何处理释放正在等待的信号量。 ## 解包 Nil 可选类型</p>
<p>Swift 编程语言是朝着编写默认安全代码迈出的重要一步。</p>
<p>Swift 的核心概念是明确地处理可选性。在类型声明中，尾随的 <code>?</code> 表示该值可以不存在，用 <code>nil</code> 表示。这些类型需要显式展开来访问它们存储的值。</p>
<p>当一个值在对象初始化时不可用，但在对象生命周期的后期，然后尾随’ !’用于保存值的类型。这意味着可以在代码中处理该值，而不需要显式解包。它被称为可选的隐式解包可选。 注意，从 Swift 4.2 开始，在实现级别，它是可选的，带有注释，表明可以使用它而无需显式解包。</p>
<p>我们使用 <code>icdab_wrap</code> 示例程序来演示由于错误使用可选控件而导致的崩溃。<span class="citation" data-cites="icdabgithub">(“IOS Crash Dump Analysis Book Github Resources” 2018)</span></p>
<h3 id="ios-uikit-outlets">iOS UIKit Outlets</h3>
<p>使用故事板来声明用户界面，并将<code>UIViews</code>与<code>UIViewController</code>相关联，这是一个标准范例。</p>
<p>当用户界面更新时，比如启动我们的应用程序时，或者在场景之间执行segue时，故事板实例支持 <code>UIViewController</code>并在我们的 <code>UIViewController</code>对象中将字段设置为已创建的 <code>UIViews</code> 。</p>
<p>当我们将故事板链接到控制器代码时，会自动生成一个字段声明，例如:</p>
<pre><code>@IBOutlet weak var planetImageOutlet: UIImageView!</code></pre>
<h3 id="所有权规则">所有权规则</h3>
<p>如果我们没有显式创建对象，并且没有将所有权传递给我们，那我们不应缩短所传递对象的生命周期。</p>
<p>在我们的<code>icdab_wrap</code> 示例中，我们有一个父页面，我们可以进入一个具有大冥王星图像的子页面。 该图像是从 Internet 下载的。 当离开该页面并访问原始页面时，代码尝试通过释放与图像关联的内存来减少内存。</p>
<p>对于这种图像清理策略是是否有用可取，存在另一种争论。应该使用一个分析工具来告诉我们什么时候应该尽量减少内存占用。</p>
<p>我们的代码存在一个 bug：</p>
<pre><code>override func viewWillDisappear(_ animated: Bool) {
        planetImageOutlet = nil
        // BUG; should be planetImageOutlet.image = nil
    }</code></pre>
<p>与其将图像视图的图像设置为 nil，不如将图像视图本身设置为 <code>nil</code>。</p>
<p>这意味着当我们重新访问Pluto场景时，由于我们的 <code>planetImageOutlet</code> 为<code>nil</code>，因此尝试存储下载的图像时会崩溃。</p>
<pre><code>func imageDownloaded(_ image: UIImage) {
        self.planetImageOutlet.image = image
    }</code></pre>
<p>该代码将崩溃，因为它隐式解包了已设置为 <code>nil</code>的可选类型。</p>
<h3 id="解包-nil-可选类型的崩溃报告">解包 nil 可选类型的崩溃报告</h3>
<p>当我们从 swift 运行时强制解包可选类型 nil 中得到崩溃时，我们看到:</p>
<pre><code>Exception Type:  EXC_BREAKPOINT (SIGTRAP)
Exception Codes: 0x0000000000000001, 0x00000001011f7ff8
Termination Signal: Trace/BPT trap: 5
Termination Reason: Namespace SIGNAL, Code 0x5
Terminating Process: exc handler [0]
Triggered by Thread:  0</code></pre>
<p>注意这是一个异常类型， <code>EXC_BREAKPOINT (SIGTRAP)</code>。</p>
<p>我们看到运行时环境由于遇到问题而引发了断点异常。这是通过查看堆栈顶部的swift核心库来识别的。</p>
<pre><code>Thread 0 name:  Dispatch queue: com.apple.main-thread
Thread 0 Crashed:
0   libswiftCore.dylib              
0x00000001011f7ff8 0x101050000 + 1736696
1   libswiftCore.dylib              
0x00000001011f7ff8 0x101050000 + 1736696
2   libswiftCore.dylib              
0x00000001010982b8 0x101050000 + 295608
3   icdab_wrap                      
0x0000000100d3d404
 PlanetViewController.imageDownloaded(_:)
  + 37892 (PlanetViewController.swift:45)
</code></pre>
<p>存在未初始化指针的另一个细微提示是机器寄存器的特殊值是 <code>0xbaddc0dedeadbead</code>。 这是由编译器设置的，以指示未初始化的指针：</p>
<pre><code>Thread 0 crashed with ARM Thread State (64-bit):
    x0: 0x0000000100ecc100   x1: 0x00000001c005b9f0   
    x2: 0x0000000000000008
       x3: 0x0000000183a4906c
    x4: 0x0000000000000080   x5: 0x0000000000000020   
    x6: 0x0048000004210103
       x7: 0x00000000000010ff
    x8: 0x00000001c00577f0   x9: 0x0000000000000000  
    x10: 0x0000000000000002
      x11: 0xbaddc0dedeadbead
   x12: 0x0000000000000001  x13: 0x0000000000000002  
   x14: 0x0000000000000000
     x15: 0x000a65756c617620
   x16: 0x0000000183b9b8cc  x17: 0x0000000000000000  
   x18: 0x0000000000000000
     x19: 0x0000000000000000
   x20: 0x0000000000000002  x21: 0x0000000000000039  
   x22: 0x0000000100d3f3d0
     x23: 0x0000000000000002
   x24: 0x000000000000000b  x25: 0x0000000100d3f40a  
   x26: 0x0000000000000014
     x27: 0x0000000000000000
   x28: 0x0000000002ffffff   fp: 0x000000016f0ca8e0   
   lr: 0x00000001011f7ff8
    sp: 0x000000016f0ca8a0   pc: 0x00000001011f7ff8
    cpsr: 0x60000000</code></pre>
<h2 id="释放正在使用的信号量">释放正在使用的信号量</h2>
<p><code>libdispatch</code>库支持识别运行时问题。 出现此类问题时，应用程序崩溃并显示异常类型， <code>EXC_BREAKPOINT (SIGTRAP)</code></p>
<p>我们使用 <code>icdab_sema</code>示例程序来演示 <code>libdispatch</code> 检测到的由于错误使用信号量而导致的崩溃。<span class="citation" data-cites="icdabgithub">(“IOS Crash Dump Analysis Book Github Resources” 2018)</span></p>
<p><code>libdispatch</code> 库是用于管理并发的操作系统库（称为Grand Central Dispatch或GCD）。该库可从Apple处以开源形式获得。<span class="citation" data-cites="libdispatchtar">(“Libdispatch Open Source” 2018)</span></p>
<p>该库抽象了操作系统如何提供对多核CPU资源的访问的详细信息。 在崩溃期间，它会向崩溃报告提供其他信息。 这意味着，如果我们愿意，我们可以找到检测到运行时问题的代码。</p>
<h3 id="释放信号量的崩溃示例">释放信号量的崩溃示例</h3>
<p><code>icdab_sema</code> 示例程序在启动时发生崩溃。 崩溃报告如下（为便于演示，将其截断）：</p>
<pre><code>Exception Type:  EXC_BREAKPOINT (SIGTRAP)
Exception Codes: 0x0000000000000001, 0x00000001814076b8
Termination Signal: Trace/BPT trap: 5
Termination Reason: Namespace SIGNAL, Code 0x5
Terminating Process: exc handler [0]
Triggered by Thread:  0

Application Specific Information:
BUG IN CLIENT OF LIBDISPATCH:
 Semaphore object deallocated while in use
Abort Cause 1

Filtered syslog:
None found

Thread 0 name:  Dispatch queue: com.apple.main-thread
Thread 0 Crashed:
0   libdispatch.dylib               0x00000001814076b8
 _dispatch_semaphore_dispose$VARIANT$mp + 76
1   libdispatch.dylib               0x00000001814067f0
 _dispatch_dispose$VARIANT$mp + 80
2   icdab_sema_ios                  0x00000001006ea98c
 use_sema + 27020 (main.m:18)
3   icdab_sema_ios                  0x00000001006ea9bc
 main + 27068 (main.m:22)
4   libdyld.dylib                   0x0000000181469fc0
 start + 4

Thread 0 crashed with ARM Thread State (64-bit):
    x0: 0x00000001c409df10   x1: 0x000000016f71ba4f
       x2: 0xffffffffffffffe0   x3: 0x00000001c409df20
    x4: 0x00000001c409df80   x5: 0x0000000000000044
       x6: 0x000000018525c984   x7: 0x0000000000000400
    x8: 0x0000000000000001   x9: 0x0000000000000000
      x10: 0x000000018140766c  x11: 0x000000000001dc01
   x12: 0x000000000001db00  x13: 0x0000000000000001
     x14: 0x0000000000000000  x15: 0x0001dc010001dcc0
   x16: 0x000000018140766c  x17: 0x0000000181404b58
     x18: 0x0000000000000000  x19: 0x00000001b38f4c80
   x20: 0x0000000000000000  x21: 0x0000000000000000
     x22: 0x00000001c409df10  x23: 0x0000000000000000
   x24: 0x0000000000000000  x25: 0x0000000000000000
     x26: 0x0000000000000000  x27: 0x0000000000000000
   x28: 0x000000016f71bb18   fp: 0x000000016f71ba70
      lr: 0x00000001814067f0
    sp: 0x000000016f71ba40   pc: 0x00000001814076b8
     cpsr: 0x80000000</code></pre>
<h3 id="错误的信号量代码">错误的信号量代码</h3>
<p>重现信号量问题的代码基于使用手动引用计数（MRC）的Xcode项目。 这是一个旧设置，但在与遗留代码库集成时可能会遇到。 在项目级别，将选项 “Objective-C Automatic Reference Counting” 设置为“NO”。 然后，我们可以直接调用 <code>dispatch_release</code> API。</p>
<p>代码如下：</p>
<pre><code>#import &lt;Foundation/Foundation.h&gt;

void use_sema() {
    dispatch_semaphore_t aSemaphore =
     dispatch_semaphore_create(1);
    dispatch_semaphore_wait(aSemaphore,
       DISPATCH_TIME_FOREVER);
    // dispatch_semaphore_signal(aSemaphore);
    dispatch_release(aSemaphore);
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        use_sema();
    }
    return 0;
}</code></pre>
<h3 id="使用特定于应用程序的崩溃报告信息">使用特定于应用程序的崩溃报告信息</h3>
<p>在我们的示例中，崩溃报告的 <code>Application Specific Information</code> 部分直接说明了问题。</p>
<pre><code>BUG IN CLIENT OF LIBDISPATCH:
 Semaphore object deallocated while in use</code></pre>
<p>我们只需要给信号量发信号就可以避免这个问题。</p>
<p>如果我们有一个更不寻常的问题，或者想更深入地了解它，我们可以查找该库的源代码，并在代码中找到相关诊断消息。</p>
<p>以下是相关的库代码：</p>
<pre><code>void
_dispatch_semaphore_dispose(dispatch_object_t dou,
        DISPATCH_UNUSED bool *allow_free)
{
    dispatch_semaphore_t dsema = dou._dsema;

    if (dsema-&gt;dsema_value &lt; dsema-&gt;dsema_orig) {
        DISPATCH_CLIENT_CRASH(
      dsema-&gt;dsema_orig - dsema-&gt;dsema_value,
                &quot;Semaphore object deallocated while in use&quot;
    );
    }

    _dispatch_sema4_dispose(&amp;dsema-&gt;dsema_sema,
     _DSEMA4_POLICY_FIFO);
}</code></pre>
<p>在这里，我们可以通过 <code>DISPATCH_CLIENT_CRASH</code> 宏查看导致崩溃的库。</p>
<h3 id="经验教训-2">经验教训</h3>
<p>在现代应用程序代码中，应避免使用手动引用计数。</p>
<p>当通过运行时库发生崩溃时，我们需要返回API规范以了解我们如何违反导致崩溃的API合同。崩溃报告中特定于应用程序的信息应该有助于我们在重新阅读API文档、研究工作样例代码和查看运行时库源代码的详细级别(如果可用)时集中注意力。</p>
<p>如果已从旧代码库中继承了MRC代码，则应使用设计模式来包装基于MRC的代码，并向其中提供干净的API。然后，该程序的其余部分可以使用自动引用计数（ARC）。这将包含问题，并允许新代码从ARC中受益。 也可以将特定文件标记为MRC。需要为文件设置编译器标志选项 <code>-fno-objc-arc</code>。可以在 Xcode 的 _Build Phases-&gt; Compile Sources_区域中找到它。</p>
<p>如果遗留代码不需要增强，则最好将其保留下来，而仅用 Facade API 对其进行包装。 然后，我们可以为该API编写一些测试用例。未主动更新的代码往往仅在以新方式使用时才会引起错误。有时，具有遗留代码知识的员工已离开项目，因此知识较少的员工进行更新可能会带来风险。</p>
<p>如果可以随时间替换旧代码，那就太好了。 通常，需要业务证明。 一种策略是将旧模块分解成较小的部分。 如果能战略性地做到这一点，那么可以采用现代编码实践对较小的部分之一进行重新加工。当增强了此类模块以解决新客户需求时，它将成为双赢。</p>
<h1 id="错误的内存崩溃">错误的内存崩溃</h1>
<p>在本章中，我们将学习错误的内存崩溃。</p>
<p>在崩溃报告中，我们可以通过异常类型 <code>EXC_BAD_ACCESS (SIGSEGV)</code> 或 <code>EXC_BAD_ACCESS (SIGBUS)</code>来进行区分。</p>
<p>我们来看看通过搜索互联网获得的一系列崩溃。</p>
<h2 id="一般原则">一般原则</h2>
<p>在操作系统中，管理内存的方法是首先将连续的内存排序为内存页，然后将页面排序为段。 这允许将元数据属性分配给应用于该段内的所有页面的段。这允许我们的程序代码(程序 <em>TEXT </em> )被设置为只读但可执行。提高了性能和安全性。</p>
<p>SIGBUS（总线错误）表示内存地址已正确映射到进程的地址区间，但不允许进程访问内存。</p>
<p>SIGSEGV（段冲突）表示存储器地址甚至没有映射到进程地址区间。</p>
<h2 id="段冲突-segv崩溃">段冲突 （SEGV）崩溃</h2>
<h3 id="fud-崩溃">fud 崩溃</h3>
<p><code>fud</code> 程序是私有框架 <code>MobileAccessoryUpdater</code>中的一个未记录的进程。</p>
<p>在这里，我们显示了macOS上进程 <code>fud</code>的崩溃报告，为了便于演示，该报告已被截断：</p>
<pre><code>Process:               fud [84641]
Path:                  /System/Library/PrivateFrameworks/
MobileAccessoryUpdater.framework/Support/fud
Identifier:            fud
Version:               106.50.4
Code Type:             X86-64 (Native)
Parent Process:        launchd [1]
Responsible:           fud [84641]
User ID:               0

Date/Time:             2018-06-12 08:34:15.054 +0100
OS Version:            Mac OS X 10.13.4 (17E199)
Report Version:        12
Anonymous UUID:        6C1D2091-02B7-47C4-5BF9-E99AD5C45875

Sleep/Wake UUID:       369D13CB-F0D3-414B-A177-38B1E560EEC7

Time Awake Since Boot: 240000 seconds
Time Since Wake:       47 seconds

System Integrity Protection: enabled

Crashed Thread:        1
  Dispatch queue: com.apple.fud.processing.queue

Exception Type:        EXC_BAD_ACCESS (SIGSEGV)
Exception Codes:       EXC_I386_GPFLT
Exception Note:        EXC_CORPSE_NOTIFY

Termination Signal:    Segmentation fault: 11
Termination Reason:    Namespace SIGNAL, Code 0xb
Terminating Process:   exc handler [0]

Thread 1 Crashed:: Dispatch queue:
 com.apple.fud.processing.queue
0   libdispatch.dylib               0x00007fff67fc6cbd
 _dispatch_continuation_push + 4
1   fud                             0x0000000101d3ce57
 __38-[FudController handleXPCStreamEvent:]_block_invoke + 593
2   libdispatch.dylib               0x00007fff67fbb64a
 _dispatch_call_block_and_release + 12
3   libdispatch.dylib               0x00007fff67fb3e08
 _dispatch_client_callout + 8
4   libdispatch.dylib               0x00007fff67fc8377
 _dispatch_queue_serial_drain + 907
5   libdispatch.dylib               0x00007fff67fbb1b6
 _dispatch_queue_invoke + 373
6   libdispatch.dylib               0x00007fff67fc8f5d
 _dispatch_root_queue_drain_deferred_wlh + 332
7   libdispatch.dylib               0x00007fff67fccd71
 _dispatch_workloop_worker_thread + 880
8   libsystem_pthread.dylib         0x00007fff68304fd2
 _pthread_wqthread + 980
9   libsystem_pthread.dylib         0x00007fff68304be9
 start_wqthread + 13

Thread 1 crashed with X86 Thread State (64-bit):
  rax: 0xe00007f80bd22039  rbx: 0x00007f80bd2202e0
    rcx: 0x7fffffffffffffff
    rdx: 0x011d800101d66da1
  rdi: 0x00007f80bd21a250  rsi: 0x0000000102c01000
    rbp: 0x0000700007e096c0
    rsp: 0x0000700007e09670
   r8: 0x0000000102c00010   r9: 0x0000000000000001
     r10: 0x0000000102c01000
     r11: 0x00000f80b5300430
  r12: 0x00007f80ba70c670  r13: 0x00007fff673c8e80
    r14: 0x00007f80bd201e00
    r15: 0x00007f80ba70cf30
  rip: 0x00007fff67fc6cbd  rfl: 0x0000000000010202
    cr2: 0x00007fff9b2f11b8

Logical CPU:     3
Error Code:      0x00000004
Trap Number:     14</code></pre>
<p>我们显然有一个不好的内存问题，因为我们有一个<code>EXC_BAD_ACCESS (SIGSEGV)</code>（SIGSEGV）异常。 我们看到的错误代码是 14，在https://github.com/apple/darwin-xnu中属于缺页中断。</p>
<p>由于 <code>libdispatch</code>是 Apple 开源的，我们甚至可以查找触发崩溃的函数。<span class="citation" data-cites="libdispatchtar">(“Libdispatch Open Source” 2018)</span></p>
<p>我们看到：</p>
<pre><code>#define dx_push(x, y, z) dx_vtable(x)-&gt;do_push(x, y, z)

DISPATCH_NOINLINE
static void
_dispatch_continuation_push(dispatch_queue_t dq,
   dispatch_continuation_t dc)
{
    dx_push(dq, dc, _dispatch_continuation_override_qos(dq, dc));
}</code></pre>
<p>我们正在从一个有错误内存位置的数据结构中解除内存引用。</p>
<p>我们可以反汇编问题调用站点的macOS二进制文件<code>/usr/lib/system/libdispatch.dylib</code>。</p>
<p>在这里，我们使用 Hopper 进行脱壳：</p>
<pre><code>__dispatch_continuation_push:
0000000000014c69 push       rbx
                             ; CODE XREF=__dispatch_async_f2+112,
                             j___dispatch_continuation_push
0000000000014c6a mov        rax, qword [rdi]
0000000000014c6d mov        r8, qword [rax+0x40]
0000000000014c71 mov        rax, qword [rsi+8]
0000000000014c75 mov        edx, eax
0000000000014c77 shr        edx, 0x8
0000000000014c7a and        edx, 0x3fff
0000000000014c80 mov        ebx, dword [rdi+0x58]
0000000000014c83 movzx      ecx, bh
0000000000014c86 je         loc_14ca3</code></pre>
<p><code>rdi</code>寄存器值似乎有问题，地址为 <code>0x00007f80bd21a250</code></p>
<p>我们需要退一步，了解为什么我们有内存访问问题。</p>
<p>查看堆栈回溯，我们可以看到该程序使用跨进程通信（XPC）来完成其工作。 它有 <code>handleXPCStreamEvent</code> 函数。</p>
<p>这是一个常见的编程问题，当我们接收到一个数据有效负载时，就会出现解压缩有效负载和解释数据的问题。我们推测反序列化代码中有一个bug。这将给我们一个潜在的坏数据结构，我们取消引用会导致崩溃。</p>
<p>如果我们是<code>fud</code>程序的作者，我们可以对其进行更新以检查它获得的XPC数据，并确保遵循最佳实践进行数据的序列化/反序列化，例如使用接口定义层生成器。</p>
<h3 id="leakagent-崩溃">LeakAgent 崩溃</h3>
<p>苹果提供了 <code>LeakAgent</code> 程序作为其内存诊断工具的一部分。 它在 Xcode Instruments 中使用。</p>
<p>以下是崩溃报告， <code>LeakAgent</code> 发生了崩溃，为了便于演示而被截断：</p>
<pre><code>Incident Identifier: 11ED1987-1BC9-4F44-900C-AD07EE6F7E26
CrashReporter Key:   b544a32d592996e0efdd7f5eaafd1f4164a2e13c
Hardware Model:      iPad6,3
Process:             LeakAgent [3434]
Path:                /Developer/Library/PrivateFrameworks/
DVTInstrumentsFoundation.framework/LeakAgent
Identifier:          LeakAgent
Version:             ???
Code Type:           ARM-64 (Native)
Role:                Unspecified
Parent Process:      DTServiceHub [1592]
Coalition:           com.apple.instruments.deviceservice
 [463]


Date/Time:           2018-07-19 14:16:57.6977 +0100
Launch Time:         2018-07-19 14:16:56.7734 +0100
OS Version:          iPhone OS 11.3 (15E216)
Baseband Version:    n/a
Report Version:      104

Exception Type:  EXC_BAD_ACCESS (SIGSEGV)
Exception Subtype: KERN_INVALID_ADDRESS at
 0x0000000000000000
VM Region Info: 0 is not in any region.
  Bytes before following region: 4371873792
      REGION TYPE                      START - END
                   [ VSIZE] PRT/MAX SHRMOD  REGION DETAIL
      UNUSED SPACE AT START
---&gt;  
      __TEXT        0000000104958000-0000000104964000
       [   48K] r-x/r-x SM=COW  ...ork/LeakAgent

Termination Signal: Segmentation fault: 11
Termination Reason: Namespace SIGNAL, Code 0xb
Terminating Process: exc handler [0]
Triggered by Thread:  4

Thread 4 name:  Dispatch queue:
DTXChannel serializer queue [x1.c0]
Thread 4 Crashed:
0   libswiftDemangle.dylib          
0x0000000104f871dc 0x104f70000 + 94684
1   libswiftDemangle.dylib          
0x0000000104f8717c 0x104f70000 + 94588
2   libswiftDemangle.dylib          
0x0000000104f86200 0x104f70000 + 90624
3   libswiftDemangle.dylib          
0x0000000104f84948 0x104f70000 + 84296
4   libswiftDemangle.dylib          
0x0000000104f833a4 0x104f70000 + 78756
5   libswiftDemangle.dylib          
0x0000000104f73290 0x104f70000 + 12944
6   CoreSymbolication               
0x000000019241d638 demangle + 112
7   CoreSymbolication               
0x00000001923d16cc
 TRawSymbol&lt;Pointer64&gt;::name+ 54988 () + 72
8   CoreSymbolication               
0x0000000192404ff4
 TRawSymbolOwnerData&lt;Pointer64&gt;::
 symbols_for_name(CSCppSymbolOwner*, char const*,
    void + 266228 (_CSTypeRef) block_pointer) + 156
9   CoreSymbolication               
0x00000001923d9734
 CSSymbolOwnerGetSymbolWithName + 116
10  Symbolication                   
0x000000019bb2e7f4
 -[VMUObjectIdentifier _targetProcessSwiftReflectionVersion]
  + 120
11  Symbolication                   
0x000000019bb2f9d8
 -[VMUObjectIdentifier loadSwiftReflectionLibrary] + 36
12  Symbolication                   
0x000000019bb29ff0
 -[VMUObjectIdentifier initWithTask:symbolicator:scanner:]
  + 436
13  Symbolication                   
0x000000019baede10
 -[VMUTaskMemoryScanner _initWithTask:options:] + 2292
14  Symbolication                   
0x000000019baee304
 -[VMUTaskMemoryScanner initWithTask:options:] + 72
15  LeakAgent                       
0x000000010495b270 0x104958000 + 12912
16  CoreFoundation                  
0x0000000183f82580 __invoking___ + 144
17  CoreFoundation                  0x0000000183e61748
 -[NSInvocation invoke] + 284
18  DTXConnectionServices           
0x000000010499f230 0x104980000 + 127536
19  DTXConnectionServices           
0x00000001049947a4 0x104980000 + 83876
20  libdispatch.dylib               0x000000018386cb24
 _dispatch_call_block_and_release + 24
21  libdispatch.dylib               0x000000018386cae4
 _dispatch_client_callout + 16
22  libdispatch.dylib               0x0000000183876a38
 _dispatch_queue_serial_drain$VARIANT$mp + 608
23  libdispatch.dylib               0x0000000183877380
 _dispatch_queue_invoke$VARIANT$mp + 336
24  libdispatch.dylib               0x0000000183877d4c
 _dispatch_root_queue_drain_deferred_wlh$VARIANT$mp + 340
25  libdispatch.dylib               0x000000018388011c
 _dispatch_workloop_worker_thread$VARIANT$mp + 668
26  libsystem_pthread.dylib         0x0000000183b9fe70
 _pthread_wqthread + 860
27  libsystem_pthread.dylib         
0x0000000183b9fb08 start_wqthread + 4

Thread 4 crashed with ARM Thread State (64-bit):
    x0: 0x0000000000000000   x1: 0x0000000000000000   
    x2: 0xfffffffffffffff6
       x3: 0x0000000000000041
    x4: 0x0000000000000000   x5: 0x0000000104f97950   
    x6: 0x0000000000000006
       x7: 0x00000000ffffffff
    x8: 0x00000001050589d0   x9: 0x0000000104f840d8  
    x10: 0xffffffffffffd544
      x11: 0x0000000000000a74
   x12: 0x0000000000000002  x13: 0x00000000000002aa  
   x14: 0x00000000000002aa
     x15: 0x00000000000003ff
   x16: 0x0000000183b96360  x17: 0x0000000000200000  
   x18: 0x0000000000000000
     x19: 0x000000016b6d1ba0
   x20: 0x00000001050589a0  x21: 0x0000000000000000  
   x22: 0x0000000000000000
     x23: 0x0000000000000001
   x24: 0x00000000ffffffff  x25: 0x0000000000000006  
   x26: 0x0000000104f97950
     x27: 0x0000000000000000
   x28: 0x0000000000000009   fp: 0x000000016b6d19c0   
   lr: 0x0000000104f8717c
    sp: 0x000000016b6d1930   pc: 0x0000000104f871dc
    cpsr: 0x60000000</code></pre>
<p>我们可以看到出错的内核地址是<code>0x0000000000000000</code>，所以它是一个空指针解引用。我们崩溃的调用站点是一个分解符号的 Swift 库。Xcode 工具试图从它在 iPad 上看到的活动中提供人类可读的对象类型定义。</p>
<p>如果我们是用户并视图分析我们的应用程序，然后在<code>LeakAgent</code>中遇到此错误，那么我们需要尝试找出避免该问题的方法。</p>
<p>由于问题是由于符号化造成的，所以明智的做法是清除构建目录，然后进行一次干净的构建。有时，Xcode更新会将我们切换到不兼容的新目标文件格式。 值得与另一个项目（可能是微不足道的测试程序）一起检查性能。 还有其他内存分析工具，例如我们正在运行的方案的诊断选项，因此可以用不同的方式进行内存分析。 有关更多信息，请参见下一章内存诊断 。</p>
<h2 id="总线错误sigbus崩溃">总线错误（SIGBUS）崩溃</h2>
<h3 id="xbmc-崩溃">xbmc 崩溃</h3>
<p><code>xbmc</code> 应用程序是一款实用程序应用程序，其作用类似于电视媒体播放器的遥控器。</p>
<p>在启动过程中，应用程序发生崩溃并产生以下崩溃报告，为便于演示，该报告已被截断：</p>
<pre><code>Incident Identifier: 396B3641-5F74-4B01-9E62-FE24A2C12E92
CrashReporter Key:   14aa0286b8b087d8b6a1ca75201a3f7d8c52d5bd
Hardware Model:      iPad1,1
Process:         XBMC [5693]
Path:            /var/mobile/Applications/
94088F35-1CDB-47CD-9D3C-328E39C2589F/XBMC.app/XBMC
Identifier:      XBMC
Version:         ??? (???)
Code Type:       ARM (Native)
Parent Process:  launchd [1]

Date/Time:       2011-04-10 11:52:44.575 +0200
OS Version:      iPhone OS 4.3.1 (8G4)
Report Version:  104

Exception Type:  EXC_BAD_ACCESS (SIGBUS)
Exception Codes: 0x00000032, 0x047001b0
Crashed Thread:  4

Thread 4 Crashed:
0   dyld                            0x2fe1c8a0 strcmp + 0
1   dyld                            0x2fe0ce32
 ImageLoaderMachO::parseLoadCmds() + 30
2   dyld                            0x2fe1262c
 ImageLoaderMachOCompressed::instantiateFromFile
 (char const*, int,
    unsigned char const*, unsigned long long,
     unsigned long long,
     stat const&amp;, unsigned int, unsigned int,
      linkedit_data_command const*,
      ImageLoader::LinkContext const&amp;) + 228
3   dyld                            0x2fe0da14
 ImageLoaderMachO::instantiateFromFile
 (char const*, int,
    unsigned char const*, unsigned long long,
     unsigned long long,
     stat const&amp;, ImageLoader::LinkContext const&amp;) + 348
4   dyld                            0x2fe052e8
 dyld::loadPhase6(int, stat const&amp;, char const*,
    dyld::LoadContext const&amp;) + 576
5   dyld                            0x2fe053fe
 dyld::loadPhase5stat(char const*,
    dyld::LoadContext const&amp;, stat*,
    int*, bool*, std::vector&lt;char const*,
    std::allocator&lt;char const*&gt; &gt;*) + 174
6   dyld                            0x2fe055b4
 dyld::loadPhase5(char const*, char const*,
    dyld::LoadContext const&amp;,
    std::vector&lt;char const*,
     std::allocator&lt;char const*&gt; &gt;*) + 232
7   dyld                            0x2fe057fe
 dyld::loadPhase4(char const*, char const*,
   dyld::LoadContext const&amp;,
    std::vector&lt;char const*,
    std::allocator&lt;char const*&gt; &gt;*) + 302
8   dyld                            0x2fe064b2
 dyld::loadPhase3(char const*, char const*,
   dyld::LoadContext const&amp;,
    std::vector&lt;char const*,
    std::allocator&lt;char const*&gt; &gt;*) + 2514
9   dyld                            0x2fe065d0
 dyld::loadPhase1(char const*, char const*,
   dyld::LoadContext const&amp;,
    std::vector&lt;char const*,
    std::allocator&lt;char const*&gt; &gt;*) + 88
10  dyld                            0x2fe06798
 dyld::loadPhase0(char const*, char const*,
   dyld::LoadContext const&amp;,
    std::vector&lt;char const*,
    std::allocator&lt;char const*&gt; &gt;*) + 368
11  dyld                            0x2fe0688e
 dyld::load(char const*, dyld::LoadContext const&amp;) + 178
12  dyld                            0x2fe08916 dlopen + 574
13  libdyld.dylib                   0x3678b4ae dlopen + 30
14  XBMC                            0x002276d4
 SoLoader::Load() (SoLoader.cpp:57)
15  XBMC                            0x0002976c
 DllLoaderContainer::LoadDll(char const*, bool)
  (DllLoaderContainer.cpp:250)
16  XBMC                            0x000299ce
 DllLoaderContainer::FindModule(char const*, char const*,
    bool) (DllLoaderContainer.cpp:147)
17  XBMC                            0x00029cca
 DllLoaderContainer::LoadModule(char const*, char const*,
    bool) (DllLoaderContainer.cpp:115)
18  XBMC                            0x0010c1a4
 CSectionLoader::LoadDLL(CStdStr&lt;char&gt; const&amp;, bool,
    bool) (SectionLoader.cpp:138)
19  XBMC                            0x000e9b10
 DllDynamic::Load() (DynamicDll.cpp:52)
20  XBMC                            0x002096c6
 ADDON::CAddonMgr::Init() (AddonManager.cpp:215)
21  XBMC                            0x004e447a
 CApplication::Create() (Application.cpp:644)
22  XBMC                            0x00510e42
 -[XBMCEAGLView runAnimation:] (XBMCEAGLView.mm:312)
23  Foundation                      0x3505b382
 -[NSThread main] + 38
24  Foundation                      
0x350cd5c6 __NSThread__main__ + 966
25  libsystem_c.dylib               
0x3035530a _pthread_start + 242
26  libsystem_c.dylib               
0x30356bb4 thread_start + 0

Thread 4 crashed with ARM Thread State:
    r0: 0x047001b0    r1: 0x2fe20ef0      r2: 0x01fe5f04
          r3: 0x2fe116d1
    r4: 0x00000001    r5: 0x01a46740      r6: 0x00000000
         r7: 0x01fe5264
    r8: 0x01a3f0fc    r9: 0x00000012     r10: 0x01fe6e60
         r11: 0x00000007
    ip: 0x2fe262f8    sp: 0x01fe5234      lr: 0x2fe0ce39
          pc: 0x2fe1c8a0
  cpsr: 0x00000010

Binary Images:
      0x1000 -   0xd98fff +XBMC armv7
        &lt;d446ccbaefe96d237cfa331a4d8216b9&gt;
         /var/mobile/Applications/
         94088F35-1CDB-47CD-9D3C-328E39C2589F/
         XBMC.app/XBMC
  0x2fe00000 - 0x2fe25fff  dyld armv7
    &lt;8dbdf7bab30e355b81e7b2e333d5459b&gt;
     /usr/lib/dyld</code></pre>
<p>在此崩溃案例中，我们通过崩溃报告异常代码部分的第二个值说明了在位置<code>0x047001b0</code> 处的错误内存：</p>
<pre><code>Exception Codes: 0x00000032, 0x047001b0</code></pre>
<p>注意，这也显示为寄存器 <code>r0</code> 的值（通常是这种情况）</p>
<p>这个值高于 XBMC 应用程序的二进制映射范围，低于崩溃报告的二进制映射部分中的 <code>dyld</code> 范围。</p>
<p>该地址必须映射到其中，但我们不知道崩溃报告将其映射到哪个段。</p>
<p>我们可以看到该应用程序可以动态配置。 从回溯中我们可以看到：</p>
<pre><code>13  libdyld.dylib                   0x3678b4ae dlopen + 30
14  XBMC                            0x002276d4
 SoLoader::Load() (SoLoader.cpp:57)</code></pre>
<p>它正在调用动态加载程序，并根据 “AddOn” 管理器确定配置加载额外的代码：</p>
<pre><code>20  XBMC                            0x002096c6
 ADDON::CAddonMgr::Init() (AddonManager.cpp:215)</code></pre>
<p>诊断此类问题的最简单方法是让应用程序在尝试在运行时加载可选软件框架之前记录其配置。 应用程序包可能缺少我们想要的库。</p>
<p>有时我们会集成第三方库，这些库中具有动态代码加载功能。 在这种情况下，我们需要使用 Xcode 诊断工具。</p>
<p>我们没有XBMC应用程序的源代码。 但是，有一个开源示例演示了动态加载程序的使用。 <span class="citation" data-cites="dynamicloadingeg">(“Dynamic Loading Example” 2018)</span></p>
<p>当我们运行该程序时，我们可以在应用程序编码的动态加载程序的使用中看到有用的消息。 此外，我们可以通过如下修改 Scheme 设置, <em>Dynamic Linker API Usage</em> :</p>
<p><img src="screenshots/dynamic_loading.png" /></p>
<p>启动该程序后，我们可以看到它如何动态加载模块。 除了我们的应用程序消息外，我们还会收到系统生成的消息。 系统消息没有时间戳前缀，但应用程序消息却有。</p>
<p>这是一个经过修剪的调试日志，显示了我们看到的输出类型:</p>
<pre><code>2018-08-18 12:26:51.989237+0100
 ios-dynamic-loading-framework[2962:109722]
 App started
2018-08-18 12:26:51.992187+0100
 ios-dynamic-loading-framework[2962:109722]
 Before referencing CASHello in DynamicFramework1
dlopen(DynamicFramework1.framework/DynamicFramework1, 0x00000001)
2018-08-18 12:26:52.002234+0100
 ios-dynamic-loading-framework[2962:109722]
 Loading CASHello in dynamic-framework-1
  dlopen(DynamicFramework1.framework/DynamicFramework1) ==&gt;
   0x600000157ce0
2018-08-18 12:26:52.002398+0100
 ios-dynamic-loading-framework[2962:109722]
 Loaded CASHello in DynamicFramework1
dlclose(0x600000157ce0)
2018-08-18 12:26:52.002560+0100
 ios-dynamic-loading-framework[2962:109722]
 CASHello from DynamicFramework1 still loaded after dlclose()
2018-08-18 12:26:52.002642+0100
 ios-dynamic-loading-framework[2962:109722]
 Before referencing CASHello in DynamicFramework2
dlopen(DynamicFramework2.framework/DynamicFramework2, 0x00000001)
objc[2962]: Class CASHello is implemented in both
 /Users/faisalm/Library/
Developer/Xcode/DerivedData/
ios-dynamic-loading-framework-ednexaanxalgpudjcqeuejsdmhlq/Build
/Products/Debug-iphonesimulator/
DynamicFramework1.framework/DynamicFramework1 (0x1229cb178)
 and
 /Users/faisalm/Library/Developer/Xcode/DerivedData/
ios-dynamic-loading-framework-ednexaanxalgpudjcqeuejsdmhlq/Build
/Products/Debug-iphonesimulator/DynamicFramework2.framework/
DynamicFramework2
 (0x1229d3178).
 One of the two will be used. Which one is undefined.
2018-08-18 12:26:52.012601+0100
 ios-dynamic-loading-framework[2962:109722]
 Loading CASHello in dynamic-framework-2
  dlopen(DynamicFramework2.framework/DynamicFramework2) ==&gt;
   0x600000157d90
2018-08-18 12:26:52.012792+0100
 ios-dynamic-loading-framework[2962:109722]
 Loaded CASHello in DynamicFramework2
dlclose(0x600000157d90)
2018-08-18 12:26:52.012921+0100
 ios-dynamic-loading-framework[2962:109722]
 CASHello from DynamicFramework2 still loaded after dlclose()</code></pre>
<p>这是加载 <code>DynamicFramework1</code>的相关源代码。</p>
<pre><code>-(void)loadCASHelloFromDynamicFramework1
{
    void *framework1Handle = dlopen(
      &quot;DynamicFramework1.framework/DynamicFramework1&quot;, RTLD_LAZY);

    if (NSClassFromString(@&quot;CASHello&quot;))
    {
        NSLog(@&quot;Loaded CASHello in DynamicFramework1&quot;);
    }
    else
    {
        NSLog(@&quot;Could not load CASHello in DynamicFramework1&quot;);
    }

    dlclose(framework1Handle);

    if (NSClassFromString(@&quot;CASHello&quot;))
    {
        NSLog(
  @&quot;CASHello from DynamicFramework1 still loaded after dlclose()&quot;
        );
    }
    else
    {
        NSLog(@&quot;Unloaded DynamicFramework1&quot;);
    }
}</code></pre>
<p>这是在的 <code>viewDidLoad</code> 中调用它的代码：</p>
<pre><code>- (void)viewDidLoad
{
    [super viewDidLoad];

    //Loading the first dynamic library here works fine :)
    NSLog(@&quot;Before referencing CASHello in DynamicFramework1&quot;);
    [self loadCASHelloFromDynamicFramework1];

    /*
     Loading the second framework will give a message in
      the console saying that both classes will be loaded
      and referencing the class will result in undefined
      behavior.
    */

    NSLog(@&quot;Before referencing CASHello in DynamicFramework2&quot;);
    [self loadCASHelloFromDynamicFramework2];
}</code></pre>
<p>通常，如果我们的应用在运行任何代码之前就崩溃了，那么最好打开 Dynamic Loader 诊断选项。这可能是部署问题（未捆绑正确的库）或代码签名问题。</p>
<h3 id="jablotron-崩溃">Jablotron 崩溃</h3>
<p><code>Jablotron</code> 程序是管理家庭中的警报和检测器的程序。</p>
<p>这是程序发生崩溃所产生的的崩溃报告，为了便于演示而被截断：</p>
<pre><code>Incident Identifier: 732438C5-9E5A-48E7-95E2-76C800CDD6D9
CrashReporter Key:   181EC21F-295A-4D13-B14E-8BE1A7DFB5C7
Hardware Model:      iPhone3,1
Process:         MyJablotron_dev [177]
Path:            /var/mobile/Applications/
D3CC3D22-1B0F-4CAF-8F68-71AD3B211CD9/
MyJablotron_dev.app/MyJablotron_dev
Identifier:      net.jablonet.myjablotron.staging
Version:         3.3.0.14 (3.3.0.14)
Code Type:       ARM
Parent Process:  launchd [1]

Date/Time:       2016-05-24T07:59:56Z
Launch Time:     2016-05-24T07:57:08Z
OS Version:      iPhone OS 7.1.2 (11D257)
Report Version:  104

Exception Type:  SIGBUS
Exception Codes: BUS_ADRALN at 0xcd0b1c
Crashed Thread:  0

Thread 0 Crashed:
0   libswiftCore.dylib    0x011aed64 0xfba000 + 2051428
1   MyJablotron_dev       0x004e7c18 0xb2000 + 4414488
2   libswiftCore.dylib    0x011b007f 0xfba000 + 2056319
3   libswiftCore.dylib    0x011aff73 0xfba000 + 2056051
4   libswiftCore.dylib    0x011adf29 0xfba000 + 2047785
5   libswiftCore.dylib    0x011adf73 0xfba000 + 2047859
6   MyJablotron_dev       0x00614a6c
 type metadata accessor for
 MyJablotron.CDFM&lt;MyJablotron.ChartDataPointStructure,
  MyJablotron.ChartDataPointStructureLegend&gt;
   (ChartThermoPlotSpace.swift:0)
7   MyJablotron_dev                      0x00606698
 MyJablotron.ChartThermoPlotSpace.init ()
 MyJablotron.ChartThermoPlotSpace
  (ChartThermoPlotSpace.swift:206)
8   MyJablotron_dev                      
0x00606c60
 MyJablotron.ChartThermoPlotSpace.__allocating_init ()
 MyJablotron.ChartThermoPlotSpace
 (ChartThermoPlotSpace.swift:0)
9   MyJablotron_dev                      
0x0048825c
 MyJablotron.ChartBase.initWithThermometer
  (__ObjC.Thermometer)()
  (ChartBase.swift:139)
10  MyJablotron_dev                      0x00488034
 MyJablotron.ChartBase.initWithSegment (__ObjC.Segment)()
  (ChartBase.swift:123)
11  MyJablotron_dev                      0x0059186c
 MyJablotron.ChartViewController.setupSegment ()()
  (ChartViewController.swift:106)
12  MyJablotron_dev                      0x0058f374
 MyJablotron.ChartViewController.viewDidLoad ()()
  (ChartViewController.swift:39)
13  MyJablotron_dev                      0x0058f5a4
 @objc MyJablotron.ChartViewController.viewDidLoad ()()
  (ChartViewController.swift:0)
14  UIKit                                0x3227d4ab
 -[UIViewController loadViewIfRequired] + 516
15  UIKit                                0x3227d269
 -[UIViewController view] + 22
16  UIKit                                0x3240936b
 -[UINavigationController
 _startCustomTransition:] + 632
17  UIKit                                0x32326d63
 -[UINavigationController
  _startDeferredTransitionIfNeeded:] + 416
18  UIKit                                0x32326b6d
 -[UINavigationController
 __viewWillLayoutSubviews] + 42
19  UIKit                                0x32326b05
 -[UILayoutContainerView layoutSubviews] + 182
20  UIKit                                0x32278d59
 -[UIView(CALayerDelegate)
 layoutSublayersOfLayer:] + 378
21  QuartzCore                           0x31ef662b
 -[CALayer layoutSublayers] + 140
22  QuartzCore                           0x31ef1e3b
 CA::Layer::layout_if_needed(CA::Transaction*) + 348
23  QuartzCore                           0x31ef1ccd
 CA::Layer::layout_and_display_if_needed(CA::Transaction*) + 14
24  QuartzCore                           0x31ef16df
 CA::Context::commit_transaction(CA::Transaction*) + 228
25  QuartzCore                           0x31ef14ef
 CA::Transaction::commit() + 312
26  QuartzCore                           0x31eeb21d
 CA::Transaction::observer_callback(__CFRunLoopObserver*,
    unsigned long, void*) + 54
27  CoreFoundation                       0x2fa27255
__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__
 + 18
28  CoreFoundation                       0x2fa24bf9
 __CFRunLoopDoObservers + 282
29  CoreFoundation                       0x2fa24f3b
 __CFRunLoopRun + 728
30  CoreFoundation                       0x2f98febf
 CFRunLoopRunSpecific + 520
31  CoreFoundation                       0x2f98fca3
 CFRunLoopRunInMode + 104
32  GraphicsServices                     0x34895663
 GSEventRunModal + 136
33  UIKit                                0x322dc14d
 UIApplicationMain + 1134
34  MyJablotron_dev                      0x002b0683
 main (main.m:16)
35  libdyld.dylib                        0x3a719ab7
 start + 0</code></pre>
<p>我们可以看到崩溃发生在 Swift Core运行时库中。 当我们看到 Apple 的通用代码崩溃时，通常表明滥用 API 。在这些情况下，我们希望看到一个描述性错误。</p>
<p>在此示例中，我们得到总线对齐错误。Apple 的库代码错误地访问了 CPU 架构的内存地址。</p>
<p>这令人惊喜。有时，当我们使用高级特性或设置编译器优化设置时，我们可能会在特殊情况或较少使用的代码路径中触发错误。</p>
<p>我们看到问题出在对象初始化期间：</p>
<pre><code>6   MyJablotron_dev                      0x00614a6c
 type metadata accessor for
 MyJablotron.CDFM&lt;MyJablotron.ChartDataPointStructure,
  MyJablotron.ChartDataPointStructureLegend&gt;
   (ChartThermoPlotSpace.swift:0)
7   MyJablotron_dev                      0x00606698
 MyJablotron.ChartThermoPlotSpace.init ()
 MyJablotron.ChartThermoPlotSpace
  (ChartThermoPlotSpace.swift:206)
8   MyJablotron_dev                      0x00606c60
 MyJablotron.ChartThermoPlotSpace.__allocating_init ()
 MyJablotron.ChartThermoPlotSpace (ChartThermoPlotSpace.swift:0)</code></pre>
<p>“元数据访问器”短语很有趣，因为它暗示我们正在运行编译器生成的代码，而不是我们直接编写的代码。 也许，作为一种解决方法，我们可以简化代码以使用更简单的语言功能。</p>
<p>在这里，我们的目标是通过采用<code>ChartThermoPlotSpace</code>类并简化它来编写一个简单的测试用例，直到找到发生崩溃的必要代码为止。</p>
<p>苹果通过更新其编译器来纠正 Swift Generics 错误，从而解决了该崩溃问题。</p>
<h1 id="应用程序中止崩溃">应用程序中止崩溃</h1>
<p>在本章中，我们研究应用程序中止崩溃。</p>
<p>通过报告异常类型来区分这些崩溃，崩溃报告中的<code>EXC_CRASH (SIGABRT)</code> 。</p>
<p>我们看见这些从网上收集的大量崩溃信息。</p>
<h2 id="一般原则-1">一般原则</h2>
<p>许多操作系统语言的支持模块和库都有用于检测致命程序错误的代码。如果触犯，操作系统终止该应用程序。 这会导致 <code>SIGABRT</code> 崩溃。</p>
<p><code>SIGABRT</code> 并没有特别的原因。所以我们我们查看各种示例，以便我们可以看到它们出现的各种情况。</p>
<p>有时，这种崩溃会在崩溃报告的 <code>Application Specific Information</code> 区域中提供一些信息。如果这不能揭示我们所需的详细信息，通常可以找到引发崩溃的模块，并对代码进行逆向工程以了解具体是什么症状。 ## Kindle Create 崩溃</p>
<p>Kindle Create是一款应用程序，作者可以利用手稿(比如 <code>docx</code> 文件)创建电子书。它通过 QuartzCore 库进行大量的绘制。</p>
<p>在发布预览时，它发生崩溃并产生以下崩溃报告，为便于演示，将其截断：</p>
<pre><code>Process:               Kindle Create [3010]
Path:                  /Applications/Kindle Create.app/
Contents/MacOS/Kindle Create
Identifier:            com.amazon.kc
Version:               1.10 (1.10)
Code Type:             X86 (Native)
Parent Process:        ??? [1]
Responsible:           Kindle Create [3010]
User ID:               501

Crashed Thread:        16  Dispatch queue:
 com.apple.root.default-qos

Exception Type:        EXC_CRASH (SIGABRT)
Exception Codes:       0x0000000000000000, 0x0000000000000000
Exception Note:        EXC_CORPSE_NOTIFY

Application Specific Information:
abort() called

Application Specific Signatures:
Graphics kernel error: 0xfffffffb

Thread 16 Crashed:: Dispatch queue: com.apple.root.default-qos
0   libsystem_kernel.dylib          0xa73e7ed6
__pthread_kill + 10
1   libsystem_pthread.dylib         0xa75a0427
pthread_kill + 363
2   libsystem_c.dylib               0xa7336956
abort + 133
3   libGPUSupportMercury.dylib      0xa2aa342d
 gpusGenerateCrashLog + 160
4   com.apple.AMDRadeonX4000GLDriver    0x180cbb00
 gpusKillClientExt + 23
5   libGPUSupportMercury.dylib      0xa2aa4857
 gpusSubmitDataBuffers + 157
6   com.apple.AMDRadeonX4000GLDriver    0x180a293c
 glrATI_Hwl_SubmitPacketsWithToken + 143
7   com.apple.AMDRadeonX4000GLDriver    0x180fd9b0
 glrFlushContextToken + 68
8   libGPUSupportMercury.dylib      0xa2aa88c8
 gldFlushContext + 24
9   GLEngine                        0x9b416f5b
 glFlushRender_Exec + 37
10  com.apple.QuartzCore            0x9c1c8412
 CA::(anonymous namespace)::IOSurface::detach() + 166
11  com.apple.QuartzCore            0x9c1c7631
 CAOpenGLLayerDraw(CAOpenGLLayer*, double, CVTimeStamp const*,
    unsigned int) + 1988
12  com.apple.QuartzCore            0x9c1c6c9a
 -[CAOpenGLLayer _display] + 618
13  com.apple.QuartzCore            0x9c179f62
 -[CALayer display] + 158
14  com.apple.AppKit                0x916106ac
 -[NSOpenGLLayer display] + 305
15  com.apple.QuartzCore            0x9c1c9f77
 display_callback(void*, void*) + 59
16  com.apple.QuartzCore            0x9c1c9efa
 CA::DispatchGroup::dispatch(bool) + 88
17  com.apple.QuartzCore            0x9c1c9e9a
 CA::DispatchGroup::callback_0(void*) + 16
18  libdispatch.dylib               0xa72565dd
 _dispatch_client_callout + 50
19  libdispatch.dylib               0xa7263679
 _dispatch_queue_override_invoke + 779
20  libdispatch.dylib               0xa725818b
 _dispatch_root_queue_drain + 660
21  libdispatch.dylib               0xa7257ea5
 _dispatch_worker_thread3 + 100
22  libsystem_pthread.dylib         0xa759cfa5
 _pthread_wqthread + 1356
23  libsystem_pthread.dylib         0xa759ca32
 start_wqthread + 34

Thread 16 crashed with X86 Thread State (32-bit):
  eax: 0x00000000  ebx: 0xb0a79000  ecx: 0xb0a78acc
    edx: 0x00000000
  edi: 0xa75a02ca  esi: 0x0000002d  ebp: 0xb0a78af8
    esp: 0xb0a78acc
   ss: 0x00000023  efl: 0x00000206  eip: 0xa73e7ed6
      cs: 0x0000000b
   ds: 0x00000023   es: 0x00000023   fs: 0x00000023
      gs: 0x0000000f
  cr2: 0xa9847340

Logical CPU:     0
Error Code:      0x00080148
Trap Number:     132

Binary Images:

0x18099000 - 0x1815efff  com.apple.AMDRadeonX4000GLDriver
 (1.68.20 - 1.6.8)
 &lt;DF3BB959-0C0A-3B6C-8E07-11B332128555&gt;
  /System/Library/Extensions/AMDRadeonX4000GLDriver.bundle/
  Contents/MacOS/AMDRadeonX4000GLDriver

0xa2aa2000 - 0xa2aacfff  libGPUSupportMercury.dylib
 (16.7.4)
 &lt;C71E29CF-D4C5-391D-8B7B-739FB0536387&gt;
  /System/Library/PrivateFrameworks/GPUSupport.framework/
  Versions/A/Libraries/libGPUSupportMercury.dylib
</code></pre>
<p>从堆栈回溯中可以看到 OpenGL 管道已刷新。 这导致 <code>com.apple.AMDRadeonX4000GLDriver</code> 检测到命令问题并触发崩溃。 我们看到该代码为崩溃报告提供了自定义信息。</p>
<pre><code>3   libGPUSupportMercury.dylib      0xa2aa342d
 gpusGenerateCrashLog + 160</code></pre>
<p>我们可以在这里使用 Hopper 脱壳和逆向工程工具。</p>
<p>通过在我们的Mac上找到二进制文件，我们可以要求Hopper不仅分解有问题的代码，而且还生成伪代码。对于大多数开发人员来说，很难立即了解汇编代码，因为当今很少使用汇编代码。这就是伪代码最有价值的原因。</p>
<p>我们首先从崩溃报告的 <code>Binary Images</code> 部分找到二进制文件的位置。</p>
<pre><code>/System/Library/PrivateFrameworks/GPUSupport.framework/
Versions/A/Libraries/libGPUSupportMercury.dylib</code></pre>
<p>对于系统二进制文件而言，遍历文件层次结构可能会很麻烦，因为它们深深地嵌套在文件系统中。</p>
<p>如果 Hopper 已经<strong>正在运行</strong>，那么快速选择正确文件的方法是使用命令行。</p>
<pre><code>&#39;/Applications/Hopper Disassembler v4.app/Contents/
MacOS/hopper&#39; -e /System/Library/PrivateFrameworks/
GPUSupport.framework/Versions/A/Libraries/
libGPUSupportMercury.dylib</code></pre>
<p>如果Hopper没有运行，我们可以启动它。我们可以启动 Finder 程序并选择 ‘Go To Folder’ 以选择文件夹</p>
<pre><code>/System/Library/PrivateFrameworks/GPUSupport.framework/
Versions/A/Libraries/</code></pre>
<p><img src="screenshots/finder_support_mercury.png" /></p>
<p>然后，我们可以简单地将<code>libGPUSupportMercury.dylib</code>从 Finder 中拖到 Hopper 应用的主面板中，它将开始处理文件。</p>
<p><img src="screenshots/drag_file_to_hopper.png" /></p>
<p>我们需要选择要脱壳的体系结构。 它必须与我们正在诊断的内容匹配。从崩溃报告中我们可以看到它是 <code>Code Type</code> <code>X86 (Native)</code>，这意味着我们需要在 Hopper 中选择32位体系结构选项。</p>
<p><img src="screenshots/hopper_32bit.png" /></p>
<p>然后我们点击 Next，然后点击 OK</p>
<p>片刻之后，文件将被处理。 然后我们可以选择 <em>Navigate -&gt; Go To Address or Symbol</em> 并提供地址 <code>_gpusGenerateCrashLog</code> 。注意，下划线是在前面的。 C 编译器会在生成目标文件之前自动将其放入。 在过去，这样做是为了使手写的汇编代码在链接期间不会与C 语言符号冲突。</p>
<p>在默认视图中，Hopper 将显示该功能的反汇编代码。</p>
<p><img src="screenshots/hopper_diss.png" /></p>
<p>通过选择伪代码按钮（用红色圆圈显示），我们可以使 Hopper 生成对该功能的更容易理解的描述。</p>
<p><img src="screenshots/hopper_pseudocode.png" /></p>
<p>这是 Hopper 的输出：</p>
<pre><code>int _gpusGenerateCrashLog(int arg0, int arg1, int arg2) {
 rdi = arg0;
 r14 = arg2;
 rbx = arg1;
 if (*0xc678 != 0x0) {
   rax = *___stack_chk_guard;
   if (rax != *___stack_chk_guard) {
     rax = __stack_chk_fail();
   }  
 }
 else {
   if (rdi != 0x0) {
     IOAccelDeviceGetName(*(rdi + 0x230), 0x0, 0x14);
   }  
   if ((rbx &amp; 0x20000000) == 0x0) {
     rdx =
     &quot;Graphics kernel error: 0x%08x\n&quot;;
   }  
   else {
     rdx =
  &quot;Graphics hardware encountered an error and was reset:
   0x%08x\n&quot;;
   }  
   sprintf_l(var_A0, 0x0, rdx);
   *0xc680 = var_A0;
   rax = abort();
 }
 return rax;
}
</code></pre>
<p>在这里，我们可以看到两种报文。一种是 ：</p>
<pre><code>&quot;Graphics kernel error: 0x%08x\n&quot;</code></pre>
<p>另一种是:</p>
<pre><code>&quot;Graphics hardware encountered an error and was reset: 0x%08x\n&quot;</code></pre>
<p>实际上，我们在崩溃报告中看到以下内容：</p>
<pre><code>Application Specific Signatures:
Graphics kernel error: 0xfffffffb</code></pre>
<p>不幸的是，尚不清楚此错误是什么意思。 我们需要应用程序的作者打开OpenGL命令级日志记录，以便了解图形驱动程序拒绝了哪些绘图命令。</p>
<p>通过使用不同的 Mac 和显卡配置将会是实验变得很有趣。让我们判断这是一个是特定的驱动问题，或一个通用的 OpenGL 问题。</p>
<h2 id="类型混淆">类型混淆</h2>
<p>编译器在静态类型检查方面做得非常出色。但在动态推断类型时，可能会出现问题。而 配置文件在这方面尤为麻烦。 很容易为配置参数设置错误的类型</p>
<p>我们借助示例代码<code>icdab_nsdata</code>来说明我们的观点。<span class="citation" data-cites="icdabgithub">(“IOS Crash Dump Analysis Book Github Resources” 2018)</span></p>
<h3 id="从配置文件中获取-nsdata-对象">从配置文件中获取 NSData 对象</h3>
<p>思考以下示例代码</p>
<pre><code>- (BOOL)application:(UIApplication *)application
 didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    // Override point for customization after application
    // launch.

    NSData *myToken = [[NSData alloc] initWithData:
    [[NSUserDefaults standardUserDefaults]
     objectForKey:@&quot;SomeKey&quot;]];

    NSLog(@&quot;My data is %@ - ok since we can handle a nil&quot;,
     myToken);

    id stringProperty = @&quot;Some string&quot;;
    NSData *problemToken = [[NSData alloc]
     initWithData:stringProperty];

    NSLog(@&quot;My data is %@ - we have probably crashed by now&quot;,
     problemToken);
    return YES;
}</code></pre>
<p>这段代码试图做两件事。 首先，它尝试从其配置中获取 <code>token</code>。我们假设在之前的运行中，用户已经以 <code>SomeKey</code>为键值保存了一个 <code>NSData</code> 格式的 <code>token</code>。</p>
<p>按照设计， <code>NSData</code> 对象可以处理所提供的数据是否为 <code>nil</code> 的情况。 因此，如果尚未保存数据，代码仍可正常运行。</p>
<p><code>token</code>可能只是一个简单的十六进制字符串，例如 <code>7893883873a705aec69e2942901f20d7b1e28dec</code></p>
<p>上面的代码中有一个字符串 <code>stringProperty</code>，用于模拟以下情况：用户存存储的 <code>token</code> 是一个字符串而不是 <code>NSData</code>对象。 可能是它被手动复制并粘贴到用户的 <code>plist</code> 文件中。 如果 <code>initWithData</code> 方法参数为 <code>NSString</code>，那么就无法创建 <code>NSData</code> 对象。 然后发生了崩溃。</p>
<p>如果我们运行该代码，我们将得到以下崩溃报告，为了便于演示，将其截断：</p>
<h3 id="反序列化崩溃报告">反序列化崩溃报告</h3>
<pre><code>Incident Identifier: 12F72C5C-E9BD-495F-A017-832E3BBF285E
CrashReporter Key:   56ec2b40764a1453466998785343f1e51c8b3849
Hardware Model:      iPod5,1
Process:             icdab_nsdata [324]
Path:                /private/var/containers/Bundle/Application/
98F79023-562D-4A76-BC72-5E56D378AD98/
icdab_nsdata.app/icdab_nsdata
Identifier:          www.perivalebluebell.icdab-nsdata
Version:             1 (1.0)
Code Type:           ARM (Native)
Parent Process:      launchd [1]
Exception Type:  EXC_CRASH (SIGABRT)
Exception Codes: 0x0000000000000000, 0x0000000000000000
Exception Note:  EXC_CORPSE_NOTIFY
Triggered by Thread:  0

Filtered syslog:
None found

Last Exception Backtrace:
0   CoreFoundation                  0x25aa3916
 __exceptionPreprocess + 122
1   libobjc.A.dylib                 0x2523ee12
 objc_exception_throw + 33
2   CoreFoundation                  0x25aa92b0
-[NSObject+ 1045168 (NSObject) doesNotRecognizeSelector:]
 + 183
3   CoreFoundation                  0x25aa6edc
 ___forwarding___ + 695
4   CoreFoundation                  0x259d2234
 _CF_forwarding_prep_0 + 19
5   Foundation                      0x2627e9a0
-[_NSPlaceholderData initWithData:] + 123
6   icdab_nsdata                    0x000f89ba
-[AppDelegate application:
didFinishLaunchingWithOptions:] + 27066 (AppDelegate.m:26)
7   UIKit                           0x2a093780
 -[UIApplication _handleDelegateCallbacksWithOptions:
 isSuspended:restoreState:] + 387
8   UIKit                           0x2a2bb2cc
 -[UIApplication _callInitializationDelegatesForMainScene:
 transitionContext:] + 3075
9   UIKit                           0x2a2bf280
-[UIApplication _runWithMainScene:transitionContext:
completion:] + 1583
10  UIKit                           0x2a2d3838
__84-[UIApplication _handleApplicationActivationWithScene:
transitionContext:completion:]_block_invoke3286 + 31
11  UIKit                           0x2a2bc7ae
 -[UIApplication workspaceDidEndTransaction:] + 129
12  FrontBoardServices              0x27146c02
 __FBSSERIALQUEUE_IS_CALLING_OUT_TO_A_BLOCK__ + 13
13  FrontBoardServices              0x27146ab4
-[FBSSerialQueue _performNext] + 219
14  FrontBoardServices              0x27146db4
-[FBSSerialQueue _performNextFromRunLoopSource] + 43
15  CoreFoundation                  0x25a65dfa
__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__
 + 9
16  CoreFoundation                  0x25a659e8
__CFRunLoopDoSources0 + 447
17  CoreFoundation                  0x25a63d56
 __CFRunLoopRun + 789
18  CoreFoundation                  0x259b3224
 CFRunLoopRunSpecific + 515
19  CoreFoundation                  0x259b3010
CFRunLoopRunInMode + 103
20  UIKit                           0x2a08cc38
 -[UIApplication _run] + 519
21  UIKit                           0x2a087184
 UIApplicationMain + 139
22  icdab_nsdata                    0x000f8830
 main + 26672 (main.m:14)
23  libdyld.dylib                   0x2565b86e tlv_get_addr
 + 41</code></pre>
<p>不幸的是，这种崩溃没有将更多的有用信息（例如“特定于应用程序的信息”）注入到崩溃报告中。</p>
<p>但是，我们确实会在系统日志（应用程序的控制台日志）中获取信息：</p>
<pre><code>default 13:36:58.000000 +0000   icdab_nsdata     
My data is &lt;&gt; - ok since we can handle a nil

default 13:36:58.000000 +0100   icdab_nsdata     
-[__NSCFConstantString _isDispatchData]:
unrecognized selector sent to instance 0x3f054

default 13:36:58.000000 +0100   icdab_nsdata
     *** Terminating app due to uncaught exception
    &#39;NSInvalidArgumentException&#39;, reason:
    &#39;-[__NSCFConstantString _isDispatchData]:
     unrecognized selector sent to instance 0x3f054&#39;

    *** First throw call stack:
    (0x25aa391b 0x2523ee17 0x25aa92b5 0x25aa6ee1 0x259d2238
     0x2627e9a5 0x3d997
     0x2a093785 0x2a2bb2d1 0x2a2bf285 0x2a2d383d 0x2a2bc7b3
      0x27146c07
      0x27146ab9 0x27146db9 0x25a65dff 0x25a659ed 0x25a63d5b
       0x259b3229
      0x259b3015 0x2a08cc3d 0x2a087189 0x3d80d 0x2565b873)

default 13:36:58.000000 +0100
    SpringBoard Application
  &#39;UIKitApplication:www.perivalebluebell.icdab-nsdata[0x51b9]&#39;
    crashed.

default 13:36:58.000000 +0100
UserEventAgent
     2769630555571: id=www.perivalebluebell.icdab-nsdata
   pid=386, state=0

default 13:36:58.000000 +0000   ReportCrash
     Formulating report for corpse[386] icdab_nsdata

default 13:36:58.000000 +0000   ReportCrash
     Saved type &#39;109(109_icdab_nsdata)&#39;
    report (2 of max 25) at
    /var/mobile/Library/Logs/CrashReporter/
    icdab_nsdata-2018-07-27-133658.ips</code></pre>
<p>从这里我们可以看到问题是 <code>__NSCFConstantString</code> 无法响应 <code>_isDispatchData</code> 是因为 <code>NSString</code> 不是数据所提供的对象。</p>
<p>Apple SDK 具有私有实现类，以支持我们使用的公共对象。 错误报告将引用这些私有类。 因此，他们的名字可能不再令人熟悉。</p>
<p>可以通过一种简单的方法进行管理，并找出具体的表示映射到要搜索的类类型定义的哪个对象。</p>
<p>方便的是，其他工程师已经在框架上使用 <code>class-dump</code> 工具来生成所有 Objective-C 的类定义，并将它们存储在 GitHub上。他们使用 <code>class-dump</code>工具。这使得 Objective-C 的所有私有框架符号都很容易搜索到。</p>
<p>我们可以找到关于 <code>_isDispatchData</code>的定义。 <span class="citation" data-cites="dispatchdata">(“NSDispatchData.h in Foundation.framework” 2018)</span></p>
<pre><code>/* Generated by RuntimeBrowser
   Image: /System/Library/Frameworks/Foundation.framework/
   Foundation
 */

@interface _NSDispatchData : NSData

+ (bool)supportsSecureCoding;

- (bool)_allowsDirectEncoding;
- (id)_createDispatchData;
- (bool)_isDispatchData;
- (Class)classForCoder;
- (id)copyWithZone:(struct _NSZone { }*)arg1;
- (void)encodeWithCoder:(id)arg1;
- (void)enumerateByteRangesUsingBlock:(id /* block */)arg1;
- (void)getBytes:(void*)arg1;
- (void)getBytes:(void*)arg1 length:(unsigned long long)arg2;
- (void)getBytes:(void*)arg1 range:(struct _NSRange
   { unsigned long long x1; unsigned long long x2; })arg2;
- (unsigned long long)hash;
- (id)initWithCoder:(id)arg1;
- (id)subdataWithRange:(struct _NSRange
  { unsigned long long x1; unsigned long long x2; })arg1;

@end</code></pre>
<p>同样，我们可以查找 <code>__NSCFConstantString</code>。 <span class="citation" data-cites="cfconstantstring">(“NSCFConstantString in Corefoundation.framework” 2018)</span></p>
<pre><code>/* Generated by RuntimeBrowser
   Image: /System/Library/Frameworks/CoreFoundation.framework/
   CoreFoundation
 */

@interface __NSCFConstantString : __NSCFString

- (id)autorelease;
- (id)copyWithZone:(struct _NSZone { }*)arg1;
- (bool)isNSCFConstantString__;
- (oneway void)release;
- (id)retain;
- (unsigned long long)retainCount;

@end</code></pre>
<h2 id="非数字-错误">“非数字” 错误</h2>
<p>在本节中，我们显示了由于错误的浮点数据而引起的崩溃。</p>
<p>我们以 macOS 上的 <code>securityAgent</code> 崩溃为例。</p>
<p>我们看到的崩溃报告如下，为了便于演示而被截断：</p>
<pre><code>Process:               SecurityAgent [99429]
Path:                  /System/Library/Frameworks/
Security.framework/Versions/A/MachServices/
SecurityAgent.bundle/Contents/
MacOS/SecurityAgent
Identifier:            com.apple.SecurityAgent
Version:               9.0 (55360.50.13)
Build Info:            SecurityAgent-55360050013000000~642
Code Type:             X86-64 (Native)
Parent Process:        launchd [1]
Responsible:           SecurityAgent [99429]
User ID:               92

Date/Time:             2018-06-18 21:39:08.261 +0100
OS Version:            Mac OS X 10.13.4 (17E202)
Report Version:        12
Anonymous UUID:        00CC683B-425F-ABF0-515A-3ED73BACDDB5

Sleep/Wake UUID:       8D3EF33B-B78C-4C76-BB7B-2F2AC3A11CEB

Time Awake Since Boot: 350000 seconds
Time Since Wake:       42 seconds

System Integrity Protection: enabled

Crashed Thread:        0  Dispatch queue:
 com.apple.main-thread

Exception Type:        EXC_CRASH (SIGABRT)
Exception Codes:       0x0000000000000000, 0x0000000000000000
Exception Note:        EXC_CORPSE_NOTIFY

Application Specific Information:
*** Terminating app due to uncaught exception
&#39;CALayerInvalidGeometry&#39;, reason:
 &#39;CALayer bounds contains NaN: [nan nan; 1424 160]&#39;
terminating with uncaught exception of type NSException
abort() called

Application Specific Backtrace 1:
0   CoreFoundation                      0x00007fff4590132b
 __exceptionPreprocess + 171
1   libobjc.A.dylib                     0x00007fff6cf7bc76
objc_exception_throw + 48
2   CoreFoundation                      0x00007fff45992dcd
+[NSException raise:format:] + 205
3   QuartzCore                          0x00007fff50ba1a72
_ZN2CA5Layer10set_boundsERKNS_4RectEb + 230
4   QuartzCore                          0x00007fff50ba190b
-[CALayer setBounds:] + 251
5   AppKit                              0x00007fff42e5ccad
-[_NSClipViewBackingLayer setBounds:] + 105
6   AppKit                              0x00007fff42e20bf0
 -[NSView(NSInternal) _updateLayerGeometryFromView] + 712
7   AppKit                              0x00007fff42eef7a2
-[NSView translateOriginToPoint:] + 191
8   AppKit                              0x00007fff42eef383
-[NSClipView _immediateScrollToPoint:] + 536
9   AppKit                              0x00007fff432c4de9
-[NSScrollAnimationHelper _doFinalAnimationStep] + 147
10  AppKit                              0x00007fff43226da2
-[NSAnimationHelper _stopRun] + 44
11  AppKit                              0x00007fff42eef089
-[NSClipView scrollToPoint:] + 202
12  AppKit                              0x00007fff42f2ab7f
-[NSScrollView scrollClipView:toPoint:] + 75
13  AppKit                              0x00007fff42ed5929
-[NSClipView _scrollTo:animateScroll:
flashScrollerKnobs:] + 1273
14  AppKit                              0x00007fff434d5750
 -[_NSScrollingConcurrentMainThreadSynchronizer
_scrollToCanonicalOrigin] + 935
15  AppKit                              0x00007fff43071d74
 -[_NSScrollingConcurrentMainThreadSynchronizer
_synchronize:completionHandler:] + 174
16  AppKit                              0x00007fff43071c94
 __80-[_NSScrollingConcurrentMainThreadSynchronizer
initWithSharedData:constantData:]_block_invoke + 145
17  libdispatch.dylib                   0x00007fff6db5be08
_dispatch_client_callout + 8
18  libdispatch.dylib                   0x00007fff6db6eed1
_dispatch_continuation_pop + 472
19  libdispatch.dylib                   0x00007fff6db5e0d1
_dispatch_source_invoke + 620
20  libdispatch.dylib                   0x00007fff6db67271
_dispatch_main_queue_callback_4CF + 776
21  CoreFoundation                      0x00007fff458b9c69
 __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 9
22  CoreFoundation                      0x00007fff4587be4a
 __CFRunLoopRun + 2586
23  CoreFoundation                      0x00007fff4587b1a3
CFRunLoopRunSpecific + 483
24  HIToolbox                           0x00007fff44b63d96
RunCurrentEventLoopInMode + 286
25  HIToolbox                           0x00007fff44b63b06
ReceiveNextEventCommon + 613
26  HIToolbox                           0x00007fff44b63884
 _BlockUntilNextEventMatchingListInModeWithFilter + 64
27  AppKit                              0x00007fff42e16a73
 _DPSNextEvent + 2085
28  AppKit                              0x00007fff435ace34
 -[NSApplication(NSEvent) _nextEventMatchingEventMask:
 untilDate:inMode:dequeue:] + 3044
29  AppKit                              0x00007fff42e0b885
 -[NSApplication run] + 764
30  AppKit                              0x00007fff42ddaa72
 NSApplicationMain + 804
31  SecurityAgent                       0x00000001007bb8b8
 main + 475
32  libdyld.dylib                       0x00007fff6db95015
 start + 1

Thread 0 Crashed:: Dispatch queue: com.apple.main-thread
0   libsystem_kernel.dylib          0x00007fff6dce5b6e
 __pthread_kill + 10
1   libsystem_pthread.dylib         0x00007fff6deb0080
 pthread_kill + 333
2   libsystem_c.dylib               0x00007fff6dc411ae
 abort + 127
3   libc++abi.dylib                 0x00007fff6bb45f8f
 abort_message + 245
4   libc++abi.dylib                 0x00007fff6bb4612b
default_terminate_handler() + 265
5   libobjc.A.dylib                 0x00007fff6cf7dea3
 _objc_terminate() + 97
6   libc++abi.dylib                 0x00007fff6bb617c9
 std::__terminate(void (*)()) + 8
7   libc++abi.dylib                 0x00007fff6bb61843
 std::terminate() + 51
8   libdispatch.dylib               0x00007fff6db5be1c
_dispatch_client_callout + 28
9   libdispatch.dylib               0x00007fff6db6eed1
 _dispatch_continuation_pop + 472
10  libdispatch.dylib               0x00007fff6db5e0d1
_dispatch_source_invoke + 620
11  libdispatch.dylib               0x00007fff6db67271
_dispatch_main_queue_callback_4CF + 776
12  com.apple.CoreFoundation        0x00007fff458b9c69
 __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 9
13  com.apple.CoreFoundation        0x00007fff4587be4a
 __CFRunLoopRun + 2586
14  com.apple.CoreFoundation        0x00007fff4587b1a3
CFRunLoopRunSpecific + 483
15  com.apple.HIToolbox             0x00007fff44b63d96
RunCurrentEventLoopInMode + 286
16  com.apple.HIToolbox             0x00007fff44b63b06
ReceiveNextEventCommon + 613
17  com.apple.HIToolbox             0x00007fff44b63884
 _BlockUntilNextEventMatchingListInModeWithFilter + 64
18  com.apple.AppKit                0x00007fff42e16a73
 _DPSNextEvent + 2085
19  com.apple.AppKit                0x00007fff435ace34
 -[NSApplication(NSEvent)
 _nextEventMatchingEventMask:untilDate:inMode:dequeue:]
  + 3044
20  com.apple.AppKit                0x00007fff42e0b885
-[NSApplication run] + 764
21  com.apple.AppKit                0x00007fff42ddaa72
 NSApplicationMain + 804
22  com.apple.SecurityAgent         0x00000001007bb8b8
 main + 475
23  libdyld.dylib                   0x00007fff6db95015
 start + 1</code></pre>
<p>这次崩溃是一个很好的示例，崩溃报告的信息部分提示了具体的错误信息。我们马上就知道在我们的层边界中有NAN（不是数字）浮点值，这就是中止的原因。</p>
<pre><code>&#39;CALayer bounds contains NaN: [nan nan; 1424 160]&#39;</code></pre>
<p>可能尚未初始化数据结构，或者发生了除零的情况。几何代码通常会创建宽高比。在启动或转折情况下，我们的帧可能为零，从而导致除以零错误，从而在我们的数据结构中产生NAN值。</p>
<p>我们注意到 Quartz（几何图形框架）使用了一些长函数。 在堆栈回溯中，我们看到：</p>
<pre><code>3   QuartzCore                          0x00007fff50ba1a72
_ZN2CA5Layer10set_boundsERKNS_4RectEb + 230</code></pre>
<p>作为一项学术练习，我们可以指出在许多错误处理案例中，哪一个在这里发挥作用。我们这里的回溯并不是叶子调用，所以偏移量是函数调用返回后将继续进行计算的地址。</p>
<p>使用 Hopper 工具，我们可以搜索符号</p>
<pre><code>__ZN2CA5Layer10set_boundsERKNS_4RectEb</code></pre>
<p>在二进制文件中</p>
<pre><code>/System/Library/Frameworks/QuartzCore.framework/
Versions/A/QuartzCore</code></pre>
<p>请注意，在搜索功能名称时，我们需要在功能名称前添加一个额外的下划线。 这是由于 C 语言编译器导致的。</p>
<p>我们看到</p>
<pre><code>__ZN2CA5Layer10set_boundsERKNS_4RectEb:        
// CA::Layer::set_bounds(CA::Rect const&amp;, bool)
0000000000008e36 push       rbp
                    ; CODE XREF=-[CALayer setBounds:]+246,
                    __ZN2CA5Layer10set_boundsERKNS_4RectEb+750
0000000000008e37 mov        rbp, rsp
0000000000008e3a push       r15</code></pre>
<p>从十六进制地址8e36，如果我们加上230，就能得到 0x8f1c。查看反编译以后的代码：</p>
<pre><code>0000000000008edd  mov   rdi, qword [objc_cls_ref_NSException]
       ; argument &quot;instance&quot; for method _objc_msgSend
0000000000008ee4  movsd xmm0, qword [r12]
0000000000008eea  movsd xmm1, qword [r12+8]
0000000000008ef1  movsd xmm2, qword [r12+0x10]
0000000000008ef8  movsd xmm3, qword [r12+0x18]
0000000000008eff  mov   rsi, qword [0x27a338]
      ; @selector(raise:format:),
                       argument &quot;selector&quot;
                       for method _objc_msgSend
0000000000008f06  lea   rdx, qword
[cfstring_CALayerInvalidGeometry]
      ; @&quot;CALayerInvalidGeometry&quot;
0000000000008f0d  lea   rcx, qword
 [cfstring_CALayer_bounds_contains_NaN____g__g___g__g_]
      ; @&quot;CALayer bounds contains NaN: [%g %g; %g %g]&quot;
0000000000008f14  mov   al, 0x4
0000000000008f16  call  qword [_objc_msgSend_24d4f8]
      ; _objc_msgSend

                     loc_8f1c:
0000000000008f1c  call  __ZN2CA11Transaction13ensure_compatEv
       ; CA::Transaction::ensure_compat(),
        CODE XREF=__ZN2CA5Layer10set_boundsERKNS_4RectEb+61,
         __ZN2CA5Layer10set_boundsERKNS_4RectEb+70,
          __ZN2CA5Layer10set_boundsERKNS_4RectEb+137,
           __ZN2CA5Layer10set_boundsERKNS_4RectEb+147</code></pre>
<p>我们可以看到 8f1c 是下一个函数调用（返回之后）。问题函数的调用在地址 8f16 处完成。我们还可以看到崩溃报告中提供的字符串文本。 <code>"CALayer bounds contains NaN: [%g %g; %g %g]"</code></p>
<h1 id="资源崩溃">资源崩溃</h1>
<p>在本站中，我们去看那些资源相关的崩溃问题。这些崩溃是由于操作系统不正确或过多使用资源而导致的。 资源崩溃由 <code>Exception Type</code> <code>EXC_RESOURCE</code> 表示。 ## CPU使用崩溃</p>
<p>iOS 平台可以自我更新。下面是一个示例，它在执行更新时消耗了太多 CPU 时间。</p>
<p>以下是崩溃报告（为了便于演示而被截断），显示了 <code>UpdateBrainService</code> 被终止了：</p>
<pre><code>Incident Identifier: 92F40C53-6BB8-4E13-A4C2-CF2F1C85E8DF
CrashReporter Key:   69face25f1299fdcbbe337b89e6a9f649818ba13
Hardware Model:      iPad4,4
Process:             
com.apple.MobileSoftwareUpdate.UpdateBrainService
 [147]
Path:                /private/var/run/
com.apple.xpcproxy.RoleAccount.staging/
com.apple.MobileSoftwareUpdate.
UpdateBrainService.16777219.47335.xpc/
com.apple.MobileSoftwareUpdate.UpdateBrainService
Identifier:          
com.apple.MobileSoftwareUpdate.UpdateBrainService
Version:             1 (1.0)
Code Type:           ARM-64 (Native)
Parent Process:      launchd [1]

Date/Time:           2015-02-03 20:14:05.504 -0800
Launch Time:         2015-02-03 20:11:35.306 -0800
OS Version:          iOS 8.1.2 (12B440)
Report Version:      105

Exception Type:  EXC_RESOURCE
Exception Subtype: CPU
Exception Message: (Limit 50%) Observed 60% over 180 secs
Triggered by Thread:  2

Thread 2 name:  Dispatch queue:
com.apple.root.default-qos
Thread 2 Attributed:
0   libsystem_kernel.dylib          
0x0000000196b9b1ec 0x196b98000 + 12780
1   ...reUpdate.UpdateBrainService
0x000000010008ac70 0x100080000 + 44144
2   ...reUpdate.UpdateBrainService
0x0000000100083678 0x100080000 + 13944
3   ...reUpdate.UpdateBrainService
0x000000010008b8e0 0x100080000 + 47328
4   ...reUpdate.UpdateBrainService
0x00000001000831e8 0x100080000 + 12776
5   ...reUpdate.UpdateBrainService
0x0000000100093478 0x100080000 + 78968
6   ...reUpdate.UpdateBrainService
0x000000010008e368 0x100080000 + 58216
7   ...reUpdate.UpdateBrainService
0x0000000100094548 0x100080000 + 83272
8   ...reUpdate.UpdateBrainService
0x000000010008ebb0 0x100080000 + 60336
9   libdispatch.dylib               
0x0000000196a713a8 0x196a70000 + 5032
10  libdispatch.dylib               
0x0000000196a71368 0x196a70000 + 4968
11  libdispatch.dylib               
0x0000000196a7d408 0x196a70000 + 54280
12  libdispatch.dylib               
0x0000000196a7e758 0x196a70000 + 59224
13  libsystem_pthread.dylib         
0x0000000196c4d2e0 0x196c4c000 + 4832
14  libsystem_pthread.dylib         
0x0000000196c4cfa4 0x196c4c000 + 4004

Thread 2 crashed with ARM Thread State (64-bit):
    x0: 0x0000000000000000   x1: 0x0000000000000000   
    x2: 0xffffffffffffffe8
       x3: 0x00000001004991c8
    x4: 0x0000000000000007   x5: 0x0000000000000018   
    x6: 0x0000000000000000
       x7: 0x0000000000000000
    x8: 0x2f6a6f72706c2e73   x9: 0x6166654448435354  
    x10: 0x5361746144746c75
      x11: 0x614264656b636174
   x12: 0x6166654448435354  x13: 0x5361746144746c75  
   x14: 0x614264656b636174
     x15: 0x007473696c702e72
   x16: 0x0000000000000154  x17: 0x00000001000cd2b1  
   x18: 0x0000000000000000
     x19: 0x000000010049963c
   x20: 0x0000000100499630  x21: 0x0000000000000000  
   x22: 0x000000014f001280
     x23: 0x0000000100499640
   x24: 0x000000014f001330  x25: 0x00000001004991a7  
   x26: 0x0000000100498c70
     x27: 0x000000019a75d0a8
   x28: 0x000000014f001330  fp: 0x0000000100499600   
   lr: 0x000000010008ac74
    sp: 0x0000000100498c50   pc: 0x0000000196b9b1ec
    cpsr: 0x80000000

Bad magic 0x86857EF8
Microstackshots: 1
(from 1969-12-31 20:33:03
   -0800 to 1969-12-31 20:33:03 -0800)
  1 ??? [0x16fd7fab0]
    1 CoreFoundation 0x1858ec000 + 37028
    [0x1858f50a4]
      1 ??? [0x16fd7f970]
        1 CoreFoundation 0x1858ec000 + 900644
        [0x1859c7e24]
          1 ??? [0x16fd7ec60]
            1 CoreFoundation 0x1858ec000 + 909008
            [0x1859c9ed0]
              1 ??? [0x16fd7ec00]
                1 libsystem_kernel.dylib 0x196b98000 + 3320
                 [0x196b98cf8]
                  1 ??? [0x16fd7ebb0]
                    1 libsystem_kernel.dylib 0x196b98000 + 3708
                     [0x196b98e7c]
                     *1 ??? [0xffffff8002012f08]</code></pre>
<p>很显然在这里<code>UpdateBrainService</code> 程序占用了太多的 CPU 资源。</p>
<pre><code>Exception Type:  EXC_RESOURCE
Exception Subtype: CPU
Exception Message: (Limit 50%) Observed 60% over 180 secs</code></pre>
<p>崩溃报告的 <code>Microstackshots</code> 部分大概告诉了我们终止时的堆栈示例。似乎报告的 <code>Bad magic</code> 值有所不同，通常与 <code>EXC_RESOURCE</code> 崩溃有关。 ## 唤醒崩溃</p>
<p>iOS平台限制唤醒次数。当CPU中断发生时，CPU会被唤醒来为某个任务提供服务。通常是在有一些 IO 需要处理时，比如入站联网数据。如果平台太频繁地唤醒，会增加电池消耗。因此，唤醒次数受到操作系统的限制。</p>
<p>以下是崩溃报告（为了便于演示而被截断），显示了 <code>Snapchat</code>被终止了：</p>
<pre><code>Incident Identifier: 79C39D6B-E4CA-4047-B96D-7EEED2B57B46
CrashReporter Key:   52be47ab0a43fb240756d6f5a1e1bcf4aa53c568
Hardware Model:      iPhone7,2
Process:             Snapchat [3151]
Path:                /private/var/mobile/Containers/
Bundle/Application/3E13B779-FFA3-491C-A018-F39E620553D4/
Snapchat.app/Snapchat
Identifier:          com.toyopagroup.picaboo
Version:             9.11.0.1 (9.11.0)
Code Type:           ARM-64 (Native)
Parent Process:      launchd [1]
Date/Time:           2015-07-05 20:00:52.228 +0200
Launch Time:         2015-07-05 19:42:01.054 +0200
OS Version:          iOS 8.3 (12F70)
Report Version:      105
Exception Type:      EXC_RESOURCE
Exception Subtype:   WAKEUPS
Exception Message:   
(Limit 150/sec) Observed 195/sec over 300 secs
Triggered by Thread: 21

Thread 21 name:  Dispatch queue:
 com.apple.avfoundation.videodataoutput.bufferqueue
Thread 21:
0       libsystem_kernel.dylib          0x193c9ce0c
 0x193c9c000 + 0xe0c
    // mach_msg_trap + 0x8
1       libsystem_kernel.dylib          0x193c9cc84
 0x193c9c000 + 0xc84
    // mach_msg + 0x44
2       IOKit                           0x182efdec0
 0x182ea8000 + 0x55ec0
    // io_connect_method + 0x168
3       IOKit                           0x182eadfd8
 0x182ea8000 + 0x5fd8
    // IOConnectCallMethod + 0xe4
4       IOSurface                       0x18bae515c
 0x18bae4000 + 0x115c
    // IOSurfaceClientLookup + 0xcc
5       IOSurface                       0x18bae90ec
 0x18bae4000 + 0x50ec
    // IOSurfaceLookup + 0x10
6       CoreMedia                       0x18252d9c8
 0x1824dc000 + 0x519c8
    // rqReceiverDequeue + 0x64
7       CoreMedia                       0x18252dd38
 0x1824dc000 + 0x51d38
    // __FigRemoteQueueReceiverSetHandler_block_invoke2 + 0xa0
8       libdispatch.dylib               0x193b71950
 0x193b70000 + 0x1950
    // _dispatch_client_callout + 0xc
9       libdispatch.dylib               0x193b8800c
 0x193b70000 + 0x1800c
    // _dispatch_source_latch_and_call + 0x800
10      libdispatch.dylib               0x193b73ab8
 0x193b70000 + 0x3ab8
    // _dispatch_source_invoke + 0x11c
11      libdispatch.dylib               0x193b7c2d0
 0x193b70000 + 0xc2d0
    // _dispatch_queue_drain + 0x7d4
12      libdispatch.dylib               0x193b74a58
 0x193b70000 + 0x4a58
    // _dispatch_queue_invoke + 0x80
13      libdispatch.dylib               0x193b7e314
 0x193b70000 + 0xe314
    // _dispatch_root_queue_drain + 0x2cc
14      libdispatch.dylib               0x193b7fc48
 0x193b70000 + 0xfc48
    // _dispatch_worker_thread3 + 0x68
15      libsystem_pthread.dylib         0x193d51228
 0x193d50000 + 0x1228
    // _pthread_wqthread + 0x32c
16      libsystem_pthread.dylib         0x193d50eec
 0x193d50000 + 0xeec
    // start_wqthread + 0x0

Thread 21 crashed with ARM Thread State (64-bit):
    x0: 0x0000000000000000   x1: 0x0000000000000003   
    x2: 0x000000000000005c
       x3: 0x00000000000010bc
    x4: 0x0000000000017c37   x5: 0x0000000000000000   
    x6: 0x0000000000000000
       x7: 0x0000000000000000
    x8: 0x00000000fffffbbf   x9: 0x0000000000000b31  
    x10: 0x000000010f67fba0
      x11: 0x0000000000000000
   x12: 0x0000000000000000  x13: 0x0000000000000001  
   x14: 0x0000000000000001
     x15: 0x0000000000000000
   x16: 0xffffffffffffffe1  x17: 0x0000000000000000  
   x18: 0x0000000000000000
     x19: 0x0000000000000000
   x20: 0x0000000000000000  x21: 0x0000000000017c37  
   x22: 0x00000000000010bc
     x23: 0x000000010f67ea20
   x24: 0x0000000000000003  x25: 0x000000000000005c  
   x26: 0x0000000000000003
     x27: 0x000000010f67fbac
   x28: 0x0000000000000000  fp: 0x000000010f67e9f0   
   lr: 0x0000000193c9cc88
    sp: 0x000000010f67e9a0   pc: 0x0000000193c9ce0c
    cpsr: 0x80000000
Bad magic 0x8B3F36FC
Microstackshots: 1
(from 1970-01-01 01:03:20 +0100 to 1970-01-01 01:03:20 +0100)
  1 ??? [0x16fddb870]
    1 CoreFoundation 0x181bd4000 + 906872
    [0x181cb1678]
      1 ??? [0x16fddab60]
        1 CoreFoundation 0x181bd4000 + 915236
        [0x181cb3724]
          1 ??? [0x16fddab00]
            1 libsystem_kernel.dylib 0x193c9c000 + 3208
            [0x193c9cc88]
              1 ??? [0x16fddaab0]
                1 libsystem_kernel.dylib 0x193c9c000 + 3596
                 [0x193c9ce0c]
                 *1 ??? [0xffffff80020144a4]</code></pre>
<p>类似于 CPU 资源受限的崩溃，我们可以得到已达到限制的指示：</p>
<pre><code>Exception Type:      EXC_RESOURCE
Exception Subtype:   WAKEUPS
Exception Message:   (Limit 150/sec) Observed 195/sec over
 300 secs</code></pre>
<p>在崩溃报告中，我们还会得到一个 <code>Bad magic</code> 值，然后是终止点正在运行的快照。</p>
<p>我们可以看到许多线程正在运行，与网络，音频和视频有关。 我们可以猜测，此应用程序在呈现世平流方面做了繁重的工作，并且此系统级别的类型代码有错误或存在性能问题。</p>
<h2 id="唤醒崩溃异常">唤醒崩溃异常</h2>
<p>在某些情况下，iOS平台可以用 <code>Exception Code</code> 对唤醒崩溃进行分类。</p>
<p>例如，错误的网络代码导致了以下崩溃，为了便于演示而将其截断：</p>
<pre><code>Exception Type:  00000020
Exception Codes: 0xbad22222
Highlighted Thread:  3

Application Specific Information:
SBUnsuspendLimit ooVoo[360] exceeded 15 wakes in 300 sec

Thread 3 name:  com.apple.NSURLConnectionLoader
Thread 3:
0   libsystem_kernel.dylib          0x307fc010
 mach_msg_trap + 20
1   libsystem_kernel.dylib          0x307fc206
 mach_msg + 50
2   CoreFoundation                  0x3569b41c
__CFRunLoopServiceMachPort + 120
3   CoreFoundation                  0x3569a154
 __CFRunLoopRun + 876
4   CoreFoundation                  0x3561d4d6
 CFRunLoopRunSpecific + 294
5   CoreFoundation                  0x3561d39e
 CFRunLoopRunInMode + 98
6   Foundation                      0x3167abc2
+[NSURLConnection(Loader) _resourceLoadLoop:] + 302
7   Foundation                      0x3167aa8a
 -[NSThread main] + 66
8   Foundation                      0x3170e59a
 __NSThread__main__ + 1042
9   libsystem_c.dylib               0x30b68c16
 _pthread_start + 314
10  libsystem_c.dylib               0x30b68ad0
 thread_start + 0</code></pre>
<p>二进制代码<code>0xbad22222</code> 读作 “Bad too many”，将“too”用作“2”的双关语。</p>
<p>在操作系统中，管理内存的方法是首先将连续的内存排序为内存页，然后将页面排序为段。 这允许将元数据属性分配给应用于该段内的所有页面的段。这允许我们的程序代码(程序 <em>TEXT </em> )被设置为只读但可执行。提高了性能和安全性。</p>
<p>此类崩溃可能是由于未正确使用网络 API. <span class="citation" data-cites="impropersockets">(“Improper Use of Bsd Sockets” 2018)</span></p>
<h2 id="设备过热崩溃">设备过热崩溃</h2>
<p>如果设备温度过高，iOS 可能会使应用程序崩溃。使用 <code>Exception Code</code> <code>0xc00010ff</code> 进行分类。</p>
<p>例如，iTrainAlarm 应用程序发生终止，并生成以下崩溃报告，为了便于演示，该报告已被截断：</p>
<pre><code>Exception Type:  00000020
Exception Codes: 0xc00010ff
Highlighted Thread:  0

Application Specific Information:
Topmost application

Thermal Level:       16
Thermal Sensors:     11336 29078 5149 3419 3437

Thread 0 name:  Dispatch queue: com.apple.main-thread
Thread 0:
0   libsystem_kernel.dylib          0x35782010
 mach_msg_trap + 20
1   libsystem_kernel.dylib          0x35782206
 mach_msg + 50
2   AppSupport                      0x360d68c4
 CPDMTwoWayMessage + 140
3   AppSupport                      0x360d52f0
-[CPDistributedMessagingCenter _sendMessage:userInfoData:
oolKey:oolData:makeServer:receiveReply:nonBlocking:error:] + 408
4   AppSupport                      0x360d59a6
-[CPDistributedMessagingCenter _sendMessage:userInfo:
receiveReply:error:toTarget:selector:context:nonBlocking:] + 870
5   AppSupport                      0x360d3cfc
 -[CPDistributedMessagingCenter _sendMessage:userInfo:
 receiveReply:error:toTarget:selector:context:] + 56
6   AppSupport                      0x360d3b8a
 -[CPDistributedMessagingCenter
  sendMessageAndReceiveReplyName:userInfo:] + 42
7   libstatusbar.dylib              0x01997c1c
 0x1995000 + 11292
8   libstatusbar.dylib              0x01997da8
 0x1995000 + 11688
9   libstatusbar.dylib              0x01997d88
 0x1995000 + 11656
10  CoreFoundation                  0x33c337f8
__CFNotificationCenterAddObserver_block_invoke_0 + 116
11  CoreFoundation                  0x33c33904
____CFXNotificationPostToken_block_invoke_0 + 124
12  CoreFoundation                  0x33c3bb2a
__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__ + 6
13  CoreFoundation                  0x33c3b158
 __CFRunLoopDoBlocks + 152
14  CoreFoundation                  0x33c3a37a
 __CFRunLoopRun + 1426
15  CoreFoundation                  0x33bbd4d6
 CFRunLoopRunSpecific + 294
16  CoreFoundation                  0x33bbd39e
 CFRunLoopRunInMode + 98
17  GraphicsServices                0x3832ffc6
 GSEventRunModal + 150
18  UIKit                           0x3162073c
 UIApplicationMain + 1084
19  iTrainAlarm                     0x000ffffc
 main (main.m:16)
20  iTrainAlarm                     0x000fffa0
 start + 32

Unknown thread crashed with unknown flavor: 5, state_count: 1</code></pre>
<p>从 <code>Exception Codes:</code> 部分我们看到异常代码 <code>0xc00010ff</code>。可以读作“Cool Off”。</p>
<p>这里表露的崩溃报告信息，显然与温度有关。目前还不清楚这个问题是特定于当前运行的应用程序、硬件的健康状况，还是发生问题时设备所在的环境。并没有已知的某些代码在运行时会产生大量热量。要解决这个问题，使用分析故障排除是合适的。例如，即使在寒冷的情况下，同一设备上的其他应用程序也发生此崩溃，那么我们可能会怀疑硬件传感器发生故障。 我们需要完整地了解问题在哪里出现，而不是在哪里没出现，以取得良好的假设。</p>
<p>最后，崩溃报告指出：</p>
<pre><code>Unknown thread crashed with unknown flavor: 5, state_count: 1</code></pre>
<p>这同样可以在系统因资源相关原因终止应用程序的情况下看到。</p>
<p>通过查阅 Darwin XNU 代码中的线程编码，我们可以看到:</p>
<pre><code>#define THREAD_STATE_NONE       5</code></pre>
<p><span class="citation" data-cites="threadstatus">(“Thread Status Values in Mach” 2018)</span></p>
<p>这就意味着这不是什么值得关注的事情。崩溃报告工具工具可以进行改进以识别并报告该线程。</p>
<h1 id="应用程序终止崩溃">应用程序终止崩溃</h1>
<p>在本章中，我们研究应用程序终止崩溃。</p>
<p>在崩溃报告中，我们可以通过异常类型 <code>EXC_CRASH (SIGKILL)</code> 来区分这些崩溃。</p>
<p>可以为应用程序中设计捕获发送给它信号的功能。通常，在 UNIX 系统中，被称为守护进程的服务器进程，就是被设计用来捕获 SIGHUP信号的。收到信号后，进程将重新读取系统配置文件。</p>
<p>然而，根据设计，某些信号无法被捕获。例如 SIGSTOP 和SIGKILL 信号。</p>
<h2 id="死锁崩溃">死锁崩溃</h2>
<p>如果在应用程序挂起之前 SQLite 文件已被锁定，则应用程序会发生崩溃。崩溃报告中解释了死锁终止原因。</p>
<p>作为一个示例，我们展示了 OneMessenger 应用程序崩溃，并提供了以下崩溃报告，为便于演示，将其截断：</p>
<pre><code>Incident Identifier: A176CFB8-6BB7-4515-A4A2-82D2B962E097
CrashReporter Key:   f02957b828fe4090389c1282ca8e38393b4e133d
Hardware Model:      iPhone9,4
Process:             OneMessenger [10627]
Path:                /private/var/containers/Bundle/Application/
03E067E9-E2C1-43F4-AC53-4E4F58131FF3/
OneMessenger.app/OneMessenger
Identifier:          com.onem.adhoc
Version:             158 (1.0.4)
Code Type:           ARM-64 (Native)
Role:                Non UI
Parent Process:      launchd [1]
Coalition:           com.onem.adhoc [3747]


Date/Time:           2017-05-10 17:37:48.6201 -0700
Launch Time:         2017-05-10 17:37:46.7161 -0700
OS Version:          iPhone OS 10.3.1 (14E304)
Report Version:      104

Exception Type:  EXC_CRASH (SIGKILL)
Exception Codes: 0x0000000000000000, 0x0000000000000000
Exception Note:  EXC_CORPSE_NOTIFY
Termination Reason: Namespace SPRINGBOARD, Code 0xdead10cc
Triggered by Thread:  0

Thread 0 name:
Thread 0 Crashed:
0   libsystem_kernel.dylib          0x000000018a337224
 mach_msg_trap + 8
1   libsystem_kernel.dylib          0x000000018a33709c
 mach_msg + 72 (mach_msg.c:103)
2   CoreFoundation                  0x000000018b308e88
 __CFRunLoopServiceMachPort + 192 (CFRunLoop.c:2527)
3   CoreFoundation                  0x000000018b306adc
 __CFRunLoopRun + 1060 (CFRunLoop.c:2870)
4   CoreFoundation                  0x000000018b236d94
 CFRunLoopRunSpecific + 424 (CFRunLoop.c:3113)
5   GraphicsServices                0x000000018cca0074
GSEventRunModal + 100 (GSEvent.c:2245)
6   UIKit                           0x00000001914ef130
 UIApplicationMain + 208 (UIApplication.m:4089)
7   OneMessenger                    0x00000001004ff1b0
 main + 88 (main.m:16)
8   libdyld.dylib                   0x000000018a24559c
 start + 4

Thread 16 name:
Thread 16:
0   libsystem_kernel.dylib          0x000000018a3394dc
 fsync + 8
1   libsqlite3.dylib                0x000000018b8b704c
unixSync + 220 (sqlite3.c:33772)
2   libsqlite3.dylib                0x000000018b8b6a5c
sqlite3PagerCommitPhaseOne + 1428 (sqlite3.c:18932)
3   libsqlite3.dylib                0x000000018b8a35a0
sqlite3BtreeCommitPhaseOne + 180 (sqlite3.c:66409)
4   libsqlite3.dylib                0x000000018b872d68
sqlite3VdbeHalt + 2508 (sqlite3.c:77161)
5   libsqlite3.dylib                0x000000018b89cb7c
sqlite3VdbeExec + 56292 (sqlite3.c:82886)
6   libsqlite3.dylib                0x000000018b88e0e0
sqlite3_step + 528 (sqlite3.c:80263)
7   OneMessenger                    0x00000001003fc2bc
__25+[DataBase executeQuery:]_block_invoke + 72 (DataBase.m:1072)
8   libdispatch.dylib               0x000000018a2129e0
_dispatch_call_block_and_release + 24 (init.c:963)
9   libdispatch.dylib               0x000000018a2129a0
_dispatch_client_callout + 16 (object.m:473)
10  libdispatch.dylib               0x000000018a220ad4
_dispatch_queue_serial_drain + 928 (inline_internal.h:2431)
11  libdispatch.dylib               0x000000018a2162cc
_dispatch_queue_invoke + 884 (queue.c:4853)
12  libdispatch.dylib               0x000000018a220fa8
_dispatch_queue_override_invoke + 344 (queue.c:4890)
13  libdispatch.dylib               0x000000018a222a50
_dispatch_root_queue_drain + 540 (inline_internal.h:2468)
14  libdispatch.dylib               0x000000018a2227d0
_dispatch_worker_thread3 + 124 (queue.c:5550)
15  libsystem_pthread.dylib         0x000000018a41b1d0
_pthread_wqthread + 1096 (pthread.c:2196)
16  libsystem_pthread.dylib         0x000000018a41ad7c
 start_wqthread + 4</code></pre>
<p>在这里我们可以看到，显然在线程 16 中，该应用程序使用了 SQLite。 我们看到终止原因：</p>
<pre><code>Termination Reason: Namespace SPRINGBOARD, Code 0xdead10cc</code></pre>
<p>请注意，死锁一词的十六进制黑话 0xdead10c。</p>
<h2 id="不安全的绘制崩溃">不安全的绘制崩溃</h2>
<p>如果应用在不安全的情况下尝试再屏幕上进行绘制，例如当前设备已经锁屏，那么我们可能会得到一个不安全的绘制崩溃。</p>
<p>例如，MobileSMS 应用程序发生终止，并生成以下崩溃报告，为了便于演示，该报告已被截断：</p>
<pre><code>Incident Identifier: B076D47C-165E-4515-8E24-2C00CD307E2E
CrashReporter Key:   475d4ae82bdeca1824ec71225197c429060bb0e3
Hardware Model:      iPhone9,3
Process:             MobileSMS [3093]
Path:                /Applications/MobileSMS.app/MobileSMS
Identifier:          com.apple.MobileSMS
Version:             1.0 (5.0)
Code Type:           ARM-64 (Native)
Role:                Foreground
Parent Process:      launchd [1]
Coalition:           com.apple.MobileSMS [799]


Date/Time:           2018-05-06 10:26:42.2201 +0300
Launch Time:         2018-05-06 10:25:13.9579 +0300
OS Version:          iPhone OS 11.1.2 (15B202)
Baseband Version:    2.01.03
Report Version:      104

Exception Type:  EXC_CRASH (SIGKILL)
Exception Codes: 0x0000000000000000, 0x0000000000000000
Exception Note:  EXC_CORPSE_NOTIFY
Termination Reason: Namespace SPRINGBOARD, Code 0x2bad45ec
Termination Description:
 SPRINGBOARD,
  Process detected doing insecure drawing while in secure mode
Triggered by Thread:  0

Filtered syslog:
None found

Thread 0 name:  Dispatch queue: com.apple.main-thread
Thread 0 Crashed:
0       CoreUI                          0x189699354
 0x189622000 + 0x77354
    // -[CUICatalog
   _resolvedRenditionKeyFromThemeRef:withBaseKey:scaleFactor:
  deviceIdiom:deviceSubtype:displayGamut:layoutDirection:
  sizeClassHorizontal:
  sizeClassVertical:memoryClass:graphicsClass:
  graphicsFallBackOrder:iconSizeIndex:] + 0x824
1       CoreUI                          0x1896993c0
 0x189622000 + 0x773c0
    // -[CUICatalog
   _resolvedRenditionKeyFromThemeRef:withBaseKey:scaleFactor:
  deviceIdiom:deviceSubtype:displayGamut:layoutDirection:
  sizeClassHorizontal:
  sizeClassVertical:memoryClass:graphicsClass:
  graphicsFallBackOrder:
  iconSizeIndex:] + 0x890
2       CoreUI                          0x189698b2c
 0x189622000 + 0x76b2c
    // -[CUICatalog
  _resolvedRenditionKeyForName:scaleFactor:
  deviceIdiom:deviceSubtype:displayGamut:layoutDirection:
  sizeClassHorizontal:
  sizeClassVertical:memoryClass:graphicsClass:
  graphicsFallBackOrder:
  withBaseKeySelector:] + 0x134

.
.
.


55      UIKit                           0x18b6982e8
 0x18b625000 + 0x732e8
    // UIApplicationMain + 0xd0
56      MobileSMS (*)                   0x10004cdd8
 0x10002c000 + 0x20dd8
    // 0x00020d58 + 0x80
57      libdyld.dylib                   0x181be656c
 0x181be5000 + 0x156c
    // start + 0x4

Binary Images (dpkg):
0x1000a8000 - 0x1000affff + TweakInject.dylib arm64
  &lt;5e43b90a0c4336c38fe56ac76a7ec1d9&gt;
   /usr/lib/TweakInject.dylib
   {&quot;install_date&quot;:&quot;2018-04-23 09:31:57 +0300&quot;,
   &quot;name&quot;:&quot;Tweak Injector&quot;,
   &quot;identifier&quot;:&quot;org.coolstar.tweakinject&quot;,
   &quot;version&quot;:&quot;1.0.6&quot;}
0x100224000 - 0x100237fff + libcolorpicker.dylib arm64
  &lt;62b3bd5a87e03646a7feda66fc69a70c&gt;
  /usr/lib/libcolorpicker.dylib
   {&quot;install_date&quot;:&quot;2018-04-25 23:17:08 +0300&quot;,
   &quot;name&quot;:&quot;libcolorpicker&quot;,
   &quot;identifier&quot;:&quot;org.thebigboss.libcolorpicker&quot;,
   &quot;version&quot;:&quot;1.6-1&quot;}
0x100244000 - 0x10024bfff + CydiaSubstrate arm64
  &lt;766a34171a3c362cae719390c6a8d715&gt;
  /Library/Frameworks/CydiaSubstrate.framework/CydiaSubstrate
   {&quot;install_date&quot;:&quot;2018-04-23 09:31:57 +0300&quot;,
   &quot;name&quot;:
   &quot;Substrate Compatibility Layer&quot;,
   &quot;identifier&quot;:&quot;mobilesubstrate&quot;,
   &quot;version&quot;:&quot;99.0&quot;}
0x100254000 - 0x100267fff + libsubstitute.0.dylib arm64
  &lt;4aa77c47f1ec362dab77d70748383ef3&gt;
   /usr/lib/libsubstitute.0.dylib
   {&quot;install_date&quot;:&quot;2018-04-23 09:31:57 +0300&quot;,
   &quot;name&quot;:&quot;Substitute&quot;,
   &quot;identifier&quot;:&quot;com.ex.libsubstitute&quot;,
   &quot;version&quot;:&quot;0.0.6-coolstar&quot;}
0x1002c0000 - 0x1002c7fff + librocketbootstrap.dylib arm64
  &lt;937a654a197136fda4826d3943045632&gt;
  /usr/lib/librocketbootstrap.dylib
   {&quot;install_date&quot;:&quot;2018-04-23 09:31:57 +0300&quot;,
   &quot;name&quot;:&quot;RocketBootstrap&quot;,
   &quot;identifier&quot;:&quot;com.rpetrich.rocketbootstrap&quot;,
   &quot;version&quot;:&quot;1.0.6&quot;}

Binary Images (App Store):

Binary Images (Other):
0x10002c000 - 0x10007bfff   MobileSMS arm64
  &lt;38a8f6a396ce3d5f99600cacce041555&gt;
   /Applications/MobileSMS.app/MobileSMS
0x100104000 - 0x100143fff   dyld arm64  
&lt;92368d6f78863cc88239f2e3ec79bba8&gt; /usr/lib/dyld</code></pre>
<p>显然，我们可以看到终止代码<code>0x2bad45ec</code>（读作 “Too Bad For Security”，对安全性来说太糟糕了）和 <code>Termination Description</code>。</p>
<pre><code>SPRINGBOARD,
Process detected doing insecure drawing while in secure mode</code></pre>
<p>我们并不期望 Apple 提供的应用程序 MobileSMS 会发生崩溃。仔细查看 <code>Binary Images</code> 部分，我们可以看到应用程序进行了 “tweaked”。由于崩溃报告中出现了 <code>CydiaSubstrate</code>，我们可以认定该手机已经越狱了。 我们不能安全的依赖这些需要具有原始设计完整性任何应用程序。也许在这款手机上，MobileSMS 应用程序进行了调整，并添加了额外的功能，但引入了一个绘制相关的 bug。</p>
<h1 id="内存诊断">内存诊断</h1>
<p>在本章中，我们着眼于解决内存问题的不同诊断选项。</p>
<h2 id="内存分配基础">内存分配基础</h2>
<p>iOS 平台在堆栈上或堆上为我们的应用分配内存。</p>
<p>每当我们在函数范围内创建局部变量时，就会在堆栈上分配内存。每当我们调用 <code>malloc</code> 方法（或其变体）时，都会从堆中分配内存。</p>
<p>堆上分配的最小的内存大小为 16 字节（我们不探究具体实现细节）。这意味着当我们不小心覆盖已分配的最小内存时，少量的内存冲突可能无法被检测到。</p>
<p>分配内存后，会将其放入虚拟内存区域。存在用于分配大致相同内存大小的虚拟内存区域。例如，我们有 <code>MALLOC_LARGE</code>，<code>MALLOC_SMALL</code> 和 <code>MALLOC_TINY</code> 三种内存区域。这种策略主要是为了减少内存碎片的数量。此外，还有一个用于存储图像字节的区域，即 <code>CGvimage</code>区域。这将允许内存优化系统性能。</p>
<p>关于发现内存分配错误的困难是，由于相邻内存可能用于不同目的，因此这些症状可能会造成混淆，所以系统的一个逻辑区域可能会干扰系统的不相关区域。 此外，由于可能存在的延迟（或等待时间），因此发现问题的时间要比引入问题的时间晚得多。</p>
<h2 id="address-sanitizer">Address Sanitizer</h2>
<blockquote>
<p>具体详见谷歌开源工具 https://github.com/google/sanitizers</p>
</blockquote>
<p>一个非常强大的工具可以协助进行内存诊断，称为 Address Sanitizer。(称为 <span class="citation" data-cites="asanchecker">Serebryany et al. (2012)</span>)</p>
<p>它需要我们为 Address Sanitizer 重新配置我们的 Scheme 设置来重编译代码：</p>
<p><img src="screenshots/diagnostic_santizer_setting.png" /></p>
<p>Address Sanitizer 会执行内存计数（成为影子内存）。他知道哪些内存位置被 “poisoned”。即，尚未分配（或已分配，然后释放）的内存。</p>
<p><span class="citation" data-cites="wwdc2015_413">(“Advanced Debugging with Address Sanitizer” 2015)</span></p>
<p>Address Sanitizer 将直接利用编译器，因此在编译代码时，对内存的任何访问都需要对影子内存进行检查，以查看内存位置是否被释放。 如果发现这种现象，则会生成错误报告。</p>
<p>这是一个非常强大的工具，因为它解决了两个最重要的内存错误类别：</p>
<ol type="1">
<li>堆内存访问溢出（Heap Buffer Overflow）</li>
<li>堆内存释放后使用（Heap Use After Free）</li>
</ol>
<p><em>堆内存访问溢出（Heap Buffer Overflow）</em> 问题是指我们将访问比已分配的内存更多的内存区域。 <em>堆内存释放后使用（Heap Use After Free）</em> 问题是指我们将访问已经释放的内存区域。</p>
<p>Address Sanitizer 可以进一步其他类型的内存问题，不过很少遇到：堆栈缓冲区溢出，全局变量溢出，C ++容器溢出以及返回错误后使用。</p>
<p>这种便利的代价是我们的程序运行将会慢 2~5 倍。但在我们的持续集成系统中消除一些问题是值得的。</p>
<h2 id="内存冲突示例">内存冲突示例</h2>
<p>思考在示例 <code>icdab_edge</code> 程序中的以下代码。 <span class="citation" data-cites="icdabgithub">(“IOS Crash Dump Analysis Book Github Resources” 2018)</span></p>
<pre><code>- (void)overshootAllocated
{
    uint8_t *memory = malloc(16);
    for (int i = 0; i &lt; 16 + 1; i++) {
        *(memory + i) = 0xff;
    }
}</code></pre>
<p>此代码分配最小的内存量，即16个字节。然后它写入17个连续的内存位置。 我们得到一个堆溢出错误。</p>
<p>这个问题本身并不会使我们的应用立即崩溃。但如果旋转设备，则会触发潜在故障，并导致崩溃。通过启用 Address Sanitizer，应用程序立即崩溃。这有一个巨大的好处。 否则，我们可能在调试屏幕旋转相关代码上浪费了很多时间。</p>
<p>Address Sanitizer 的错误报告很详尽。 为了便于演示，我们仅显示报告的选定部分。</p>
<p>错误报告以以下内容开头：</p>
<pre><code>==21803==ERROR: AddressSanitizer:
 heap-buffer-overflow on address
 0x60200003a5e0 at
  pc 0x00010394461b bp 0x7ffeec2b8f00 sp 0x7ffeec2b8ef8
WRITE of size 1 at 0x60200003a5e0 thread T0
#0 0x10394461a in -[Crash overshootAllocated] Crash.m:48</code></pre>
<p>这就足够我们切换到代码并开始理解问题。</p>
<p>并进一步提供了详细信息，显示我们的访问超出所分配的 16 字节的内存区域。</p>
<pre><code>0x60200003a5e0 is located 0 bytes to the right of
 16-byte region [0x60200003a5d0,0x60200003a5e0)
allocated by thread T0 here:
#0 0x103bcdaa3 in wrap_malloc
(libclang_rt.asan_iossim_dynamic.dylib:x86_64+0x54aa3)
#1 0x1039445ae in -[Crash overshootAllocated] Crash.m:46</code></pre>
<p>请注意使用 “半开” 数字范围数字表示法，其中<code>[</code>包括较低范围的索引，而 <code>)</code> 则排除较高范围的索引。 因此，我们对<code>0x60200003a5e0</code>的访问超出了分配的范围 <code>[0x60200003a5d0,0x60200003a5e0）</code>。</p>
<p>我们还得到了围绕该问题的内存的“映射”，为了便于演示，将 <code>0x1c0400007460</code> 截断为 <code>.... 7460</code>：</p>
<pre><code>SUMMARY: AddressSanitizer: heap-buffer-overflow Crash.m:48 in
 -[Crash overshootAllocated]
Shadow bytes around the buggy address:
....7460: fa fa 00 00 fa fa fd fd fa fa fd fa fa fa fd fa
....7470: fa fa 00 00 fa fa fd fa fa fa 00 00 fa fa fd fd
....7480: fa fa fd fa fa fa fd fd fa fa fd fa fa fa fd fa
....7490: fa fa fd fd fa fa fd fd fa fa fd fa fa fa fd fa
....74a0: fa fa 00 fa fa fa 00 00 fa fa fd fd fa fa 00 00
=&gt;....74b0: fa fa 00 00 fa fa 00 00 fa fa 00 00[fa]fa fa fa
....74c0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
....74d0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
....74e0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
....74f0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
....7500: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend
(one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07
  Heap left redzone:       fa</code></pre>
<p>从<code>[fa]</code> 中我们看到我们命中了 “redzone”（ <code>poisoned</code> 内存）的第一个字节。</p>
<h2 id="堆内存释放后使用示例">（堆内存）释放后使用示例</h2>
<p>思考在示例 <code>icdab_edge</code> 程序中的以下代码。<span class="citation" data-cites="icdabgithub">(“IOS Crash Dump Analysis Book Github Resources” 2018)</span></p>
<pre><code>- (void)useAfterFree
{
    uint8_t *memory = malloc(16);         // line 54
    for (int i = 0; i &lt; 16; i++) {
        *(memory + i) = 0xff;
    }
    free(memory);                         // line 58
    for (int i = 0; i &lt; 16; i++) {
        *(memory + i) = 0xee;             // line 60
    }
}</code></pre>
<p>该代码分配最小的内存空间（16个字节），然后写入该内存，将其释放，然后再次尝试写入同一内存。</p>
<p>Address Sanitizer 报告报告我们访问已经释放的内存的位置：</p>
<pre><code>35711==ERROR: AddressSanitizer:
 heap-use-after-free on address
0x602000037270 at
 pc 0x000106d34381 bp 0x7ffee8ec9ef0 sp 0x7ffee8ec9ee8
WRITE of size 1 at 0x602000037270 thread T0
    #0 0x106d34380 in -[Crash useAfterFree] Crash.m:60</code></pre>
<p>它告诉我们释放的位置：</p>
<pre><code>0x602000037270 is located 0 bytes inside of 16-byte region
 [0x602000037270,0x602000037280)
freed by thread T0 here:
    #0 0x106fbdc6d in wrap_free
    (libclang_rt.asan_iossim_dynamic.dylib:x86_64+0x54c6d)
    #1 0x106d34318 in -[Crash useAfterFree] Crash.m:58</code></pre>
<p>它告诉我们内存最初分配的位置：</p>
<pre><code>previously allocated by thread T0 here:
    #0 0x106fbdaa3 in wrap_malloc
    (libclang_rt.asan_iossim_dynamic.dylib:x86_64+0x54aa3)
    #1 0x106d3428e in -[Crash useAfterFree] Crash.m:54
    SUMMARY: AddressSanitizer: heap-use-after-free Crash.m:60 in
     -[Crash useAfterFree]</code></pre>
<p>最后，它向我们展示了一个错误地址周围的内存分布图片，为了便于演示，将“ 0x1c0400006df0”截断为“ …. 6df0”：</p>
<pre><code>    Shadow bytes around the buggy address:
  ....6df0: fa fa fd fd fa fa 00 00 fa fa fd fd fa fa fd fa
  ....6e00: fa fa fd fa fa fa 00 00 fa fa fd fa fa fa 00 00
  ....6e10: fa fa fd fd fa fa fd fa fa fa fd fd fa fa fd fa
  ....6e20: fa fa fd fa fa fa fd fd fa fa fd fd fa fa fd fa
  ....6e30: fa fa fd fa fa fa 00 fa fa fa 00 00 fa fa fd fd
=&gt;....6e40: fa fa 00 00 fa fa 00 00 fa fa 00 00 fa fa[fd]fd
  ....6e50: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  ....6e60: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  ....6e70: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  ....6e80: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  ....6e90: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
    Shadow byte legend
    (one shadow byte represents 8 application bytes):
      Addressable:           00
      Partially addressable: 01 02 03 04 05 06 07
      Heap left redzone:       fa
      Freed heap region:       fd</code></pre>
<p>我们看到条目<code>[fd]</code>表示已释放对内存的进行写入操作。</p>
<h2 id="内存管理工具">内存管理工具</h2>
<p>有一系列补充Address Sanitizer的工具。在关掉 Address Sanitizer 之后在使用他们。他们会发现 Address Sanitizer 可能会错过的某些失败原因。</p>
<p>与 Address Sanitizer 相比，这些内存管理工具不需要重新编译项目。</p>
<h3 id="guard-malloc-工具">Guard Malloc 工具</h3>
<p>该工具的一个主要缺点是仅适用于模拟器。每块内存都被分配在他们自己的内存页中，前后都有保护。该工具在很大程度上已被 Address Sanitizer 取代。</p>
<h3 id="malloc-scribble">Malloc Scribble</h3>
<p>Malloc Scribble 的目的是通过将 <code>malloc</code> 的或 <code>free</code> 的内存标记成固定的已知值来使内存错误的症状变得可预测。已分配的内存标记为<code>0xAA</code>而已释放的内存标记为 <code>0x55</code>。 它不会影响在堆栈上分配内存的行为。 它与Address Sanitizer不兼容。</p>
<p>如果我们有一个应用程序每次在运行时都以不同的方式崩溃，那么 Malloc Scribble 是一个不错的选择。 它将有助于使崩溃可预测和可重复。</p>
<p>思考在示例 <code>icdab_edge</code> 程序中的以下代码。<span class="citation" data-cites="icdabgithub">(“IOS Crash Dump Analysis Book Github Resources” 2018)</span></p>
<pre><code>- (void)uninitializedMemory
{
    uint8_t *source = malloc(16);
    uint8_t target[16] = {0};
    for (int i = 0; i &lt; 16; i++) {
       target[i] = *(source + i);
    }
}</code></pre>
<p>首先，为 <code>source</code> 提供新分配的内存。由于此内存尚未初始化，因此在 Scheme 设置中设置了Malloc Scribble（以及 Address Sanitizer 重置）后，会将其设置为0xAA。</p>
<p>然后，设置 <code>target</code>。它是堆栈上的缓冲区（不是堆内存）。使用代码 <code>= {0}</code>，我们将应用设置的缓冲区的所有内存位置设置为 <code>0</code>。 否则，它将是随机存储器值。</p>
<p>然后我们进入一个循环。 通过在调试器中进行断点（例如在第二次迭代中），我们可以打印出内存内容并看到以下内容：</p>
<p><img src="screenshots/scribble.png" /></p>
<p>我们看到 <code>target</code>缓冲区与前两个索引位置（ <code>0xAA</code>）相距零。我们看到 <code>source</code>内存始终为 <code>0xAA</code>。</p>
<p>如果我们没有设置Malloc Scribble，则目标缓冲区将被填充随机值。在复杂的程序中，此类数据可以被输入到影响程序行为的其他子系统中。</p>
<h3 id="zombie-objects僵尸对象">Zombie Objects（僵尸对象）</h3>
<p>僵尸对象的目的是在Objective-C 环境中的中检测 <code>NSObject</code>对象释放后使用的错误。特别是如果我们有一个使用手动引用计数的遗留代码库，则很容易过度释放对象。这意味着，通过指针传递消息可能会产生不可预测的效果。</p>
<p>此设置只能在调试构建时进行，因为代码将不再释放对象。它的性能配置文件相当于泄漏所有应该被释放的对象。</p>
<p>开启配置将使释放对象成为<code>NSZombie</code>对象。 发送给<code>NSZombie</code>对象的任何消息都会导致立即崩溃。 因此，每当有消息发送给已经释放的的对象时，我们都可以确保发生崩溃。</p>
<p>思考在示例 <code>icdab_edge</code> 程序中的以下代码。<span class="citation" data-cites="icdabgithub">(“IOS Crash Dump Analysis Book Github Resources” 2018)</span></p>
<pre><code>- (void)overReleasedObject
{
    id vc = [[UIViewController alloc] init];
    // Build Phases -&gt; Compile Sources
    // -&gt; Crash.m has Compiler Flags setting
    // -fno-objc-arc to allow the following line to be called
    [vc release];
    NSLog(@&quot;%@&quot;, [vc description]);
}</code></pre>
<p>当调用上述代码时，会发生崩溃，并记录以下内容：</p>
<pre><code>2018-09-12 12:09:10.236058+0100 icdab_edge[92796:13650378]
 *** -[UIViewController description]: message sent to deallocated
  instance 0x7fba1ff071c0</code></pre>
<p>查看调试器，我们看到：</p>
<p><img src="screenshots/zombie.png" /></p>
<p>注意对象实例<code>vc</code>的类型是<code>_NSZombie_UIViewController *</code>。</p>
<p>该类型将是过已释放对象的原始类型，但是增加前缀了 <code>_NSZombie_</code>。 这是最有帮助的，在调试器中研究程序状态时，我们应该注意这一点。</p>
<h3 id="malloc-堆栈">Malloc 堆栈</h3>
<p>有时需要了解我们应用程序过去的动态行为，以解决应用程序崩溃的原因。 例如，我们可能发生了内存泄露，然后由于使用过多内存而被系统终止。 我们可能有一个数据结构，想知道代码的哪一部分负责分配它。</p>
<p>Malloc Stack 选项的目的是为提供我们所需的历史数据。苹果通过提供补充的可视化工具来增强了内存分析。 Malloc Stack 具有一个子选项“所有分配和释放的历史记录”或“仅实时分配”。</p>
<p>我们建议使用“所有分配”选项，除非使用的开销过多。 那可能是由于有一个应用程序大量使用了内存分配。 “仅实时分配”选项足以捕获内存泄漏，并且开销很低，因此它是用户界面中的默认选项。</p>
<p>下面是执行步骤：</p>
<ol type="1">
<li>在 <code>Diagnostics</code> 设置选项卡中设置<code>Malloc Stack</code>选项。</li>
<li>启动应用.</li>
<li>按下 <code>Debug Memgraph</code> 按钮</li>
<li>基于命令行工具进行分析，<em>File -&gt; Export Memory Graph…</em></li>
</ol>
<p>Xcode 内的 Memgraph 可视化工具功能全面，但令人生畏。 有一个有用的WWDC视频来介绍基本知识 <span class="citation" data-cites="wwdc2018_416">(“IOS Memory Deep Dive” 2018)</span></p>
<p>通常有太多的底层细节需要检查。使用图形工具的最佳方式是当我们对应用程序错误使用内存有一些假设时。</p>
<h4 id="malloc-堆栈存储器示例检测循环引用">Malloc 堆栈存储器示例：检测循环引用</h4>
<p>一个简单有效的成果是看看我们是否有内存泄漏。这些内存位置无法访问，不能释放。</p>
<p>我们使用tvOS示例应用程序 <code>icdab_cycle</code> 来展示 Memgraph 如何发现循环引用。 <span class="citation" data-cites="icdabgithub">(“IOS Crash Dump Analysis Book Github Resources” 2018)</span></p>
<p>通过在 Scheme 设置中设置Malloc Stack ，我们启动应用程序，然后按Memgraph按钮，如下所示：</p>
<p><img src="screenshots/memgraphbutton.png" /></p>
<p>通过按下感叹号过滤器按钮，我们可以过滤为仅显示泄漏：</p>
<p><img src="screenshots/retaincycle.png" /></p>
<p>如果已经完成 <em>File-&gt; Export Memory Graph …</em>，将内存图导出到 <code>icdab_cycle.memgraph</code>，我们可以使用以下命令从Mac Terminal应用程序中查看等效信息：</p>
<pre><code>leaks icdab_cycle.memgraph

Process:         icdab_cycle [15295]
Path:            /Users/faisalm/Library/Developer/
CoreSimulator/Devices/
1616CA04-D1D0-4DF6-BE8E-F63541EC1EED/
data/Containers/Bundle/Application/
E44B9EFD-258B-4D0E-8637-CF374638D5FF/
icdab_cycle.app/icdab_cycle
Load Address:    0x106eb7000
Identifier:      icdab_cycle
Version:         ???
Code Type:       X86-64
Parent Process:  debugserver [15296]

Date/Time:       2018-09-14 16:38:23.008 +0100
Launch Time:     2018-09-14 16:38:12.398 +0100
OS Version:      Apple TVOS 12.0 (16J364)
Report Version:  7
Analysis Tool:   /Users/faisalm/Downloads/
Xcode.app/Contents/Developer/Platforms/
AppleTVOS.platform/Developer/Library/CoreSimulator/
Profiles/Runtimes/
tvOS.simruntime/Contents/
Resources/RuntimeRoot/Developer/Library/
PrivateFrameworks/DVTInstrumentsFoundation.framework/
LeakAgent
Analysis Tool Version:  iOS Simulator 12.0 (16J364)

Physical footprint:         38.9M
Physical footprint (peak):  39.0M
----

leaks Report Version: 3.0
Process 15295: 30252 nodes malloced for 5385 KB
Process 15295: 3 leaks for 144 total leaked bytes.

Leak: 0x600000d506c0  size=64  zone:
DefaultMallocZone_0x11da72000
   Song  Swift  icdab_cycle
    Call stack: [thread 0x10a974380]: |
   0x10a5f678d (libdyld.dylib) start |
    0x106eba614 (icdab_cycle) main
     AppDelegate.swift:12 ....</code></pre>
<p>导致内存泄漏的代码是：</p>
<pre><code>var mediaLibrary: Album?

func createRetainCycleLeak() {
    let salsa = Album()
    let carnaval = Song(album: salsa,
     artist: &quot;Salsa Latin 100%&quot;,
     title: &quot;La Vida Es un Carnaval&quot;)
    salsa.songs.append(carnaval)
}

func buildMediaLibrary() {
    let kylie = Album()
    let secret = Song(album: kylie,
     artist: &quot;Kylie Minogue&quot;,
     title: &quot;It&#39;s No Secret&quot;)
    kylie.songs.append(secret)
    mediaLibrary = kylie
    createRetainCycleLeak()
}</code></pre>
<p>这个问题是因为 <code>createRetainCycleLeak()</code> 方法中的<code>Song</code> 的实例对象 <code>carnaval</code> 强引用了 <code>Album</code>的实例对象<code>salsa</code>，而 <code>salsa</code> 又强引用了 <code>Song</code> 的实例对象 <code>carnaval</code> ，而当我们从这个方法返回时，并没有从其他对象引用任何一个对象。这两个对象与对象图的其他部分断开连接，并且由于它们的相互强引用(称为循环引用)，所以它们不能被自动释放。一个非常类似的对象间的关系如 <code>Album</code> 的实例对象<code>kylie</code>并没有引起内存泄露，因为他是被顶级对象 <code>mediaLibrary</code> 所引用的。</p>
<h3 id="动态链接器api的用法">动态链接器API的用法</h3>
<p>有时程序是动态适应的或可扩展的。对于这样的程序，动态链接器 AP I 将以编程方式加载额外的代码模块。当应用程序的配置或部署出错时，可能会导致崩溃。</p>
<p>要调试这些问题，需要设置 <code>Dynamic Linker API Usage</code> 。但这可能会产生很多信息，因此可能会在启动时间有限、速度较慢的平台上造成问题，比如第一代Apple Watch</p>
<p>GitHub 上提供了使用动态链接器的示例应用程序。 <span class="citation" data-cites="dynamicloadingeg">(“Dynamic Loading Example” 2018)</span></p>
<p>启用后的输出为：</p>
<pre><code>_dyld_get_image_slide(0x105545000)
_dyld_register_func_for_add_image(0x10a21264c)
_dyld_get_image_slide(0x105545000)
_dyld_get_image_slide(0x105545000)
_dyld_register_func_for_add_image(0x10a5caf39)
dyld_image_path_containing_address(0x1055d2000)
.
.
.

dlopen(DynamicFramework2.framework/DynamicFramework2)
 ==&gt; 0x60c0001460f0
.
.
.</code></pre>
<p>这会生成大量日志记录。对此最好先搜索 <code>dlopen</code> 命令，然后查看 <code>dlopen</code> 系列中的其他函数被调用了</p>
<h3 id="动态库加载">动态库加载</h3>
<p>在初始化阶段，有时会出现应用程序崩溃，此时动态加载器正在加载应用程序二进制文件及其依赖框架。 如果我们确信它不是使用动态链接器API的自定义代码，而是将框架组装到我们关心的已加载二进制文件中，那么选择 <code>Dynamic Library Loads</code> 是合适的。 与启用 <code>Dynamic Linker API Usage</code> 相比，我们得到的日志要短得多。</p>
<p>启动后，我们将获得加载的二进制文件列表：</p>
<pre><code>dyld: loaded: /Users/faisalm/Library/Developer/
CoreSimulator/Devices/
99DB717F-9161-461A-B11F-210C389ABA12/
data/Containers/Bundle/Application/
D916AC0F-6434-46A3-B18E-5EC65D194454/
icdab_edge.app/icdab_edge

dyld: loaded: /Applications/Xcode.app/Contents/Developer/
Platforms/iPhoneOS.platform/Contents/Resources/RuntimeRoot/
usr/lib/libBacktraceRecording.dylib
.
.
.</code></pre>
<h1 class="unnumbered" id="参考书目">参考书目</h1>
<div id="refs" class="references hanging-indent" role="doc-bibliography">
<div id="ref-macbookproT2">
<p>“2018 Macbook Pros Bridge Os Error.” 2018. <a href="https://forums.macrumors.com/threads/2018-macbook-pros-crashing-with-bridge-os-error.2128976/">https://forums.macrumors.com/threads/2018-macbook-pros-crashing-with-bridge-os-error.2128976/</a>.</p>
</div>
<div id="ref-wwdc2015_413">
<p>“Advanced Debugging with Address Sanitizer.” 2015. <a href="https://developer.apple.com/videos/play/wwdc2015/413/">https://developer.apple.com/videos/play/wwdc2015/413/</a>.</p>
</div>
<div id="ref-kepnertregoe">
<p>“Analytic Troubleshooting.” 2018. <a href="https://www.kepner-tregoe.com">https://www.kepner-tregoe.com</a>.</p>
</div>
<div id="ref-appleinsiderimacpro">
<p>“Apple’s T2 Chip May Be Behind iMac Pro, Macbook Pro Crashes.” 2018. <a href="https://appleinsider.com/articles/18/07/26/apples-t2-chip-could-be-behind-small-number-of-crashes-in-imac-pro-new-macbook-pro">https://appleinsider.com/articles/18/07/26/apples-t2-chip-could-be-behind-small-number-of-crashes-in-imac-pro-new-macbook-pro</a>.</p>
</div>
<div id="ref-tn2123">
<p>“CrashReport Technote 2123.” 2004. <a href="https://developer.apple.com/library/archive/technotes/tn2004/tn2123.html">https://developer.apple.com/library/archive/technotes/tn2004/tn2123.html</a>.</p>
</div>
<div id="ref-intelrob">
<p>“Debugging Processor Reorder Buffer Timeout: Guide.” 2018. <a href="https://www.intel.co.uk/content/www/uk/en/embedded/training/rob-timeout-debug-guide-paper.html?wapkw=caterr">https://www.intel.co.uk/content/www/uk/en/embedded/training/rob-timeout-debug-guide-paper.html?wapkw=caterr</a>.</p>
</div>
<div id="ref-dynamicloadingeg">
<p>“Dynamic Loading Example.” 2018. <a href="https://github.com/patriknyblad/ios-runtime-loading-dynamic-framework.git">https://github.com/patriknyblad/ios-runtime-loading-dynamic-framework.git</a>.</p>
</div>
<div id="ref-impropersockets">
<p>“Improper Use of Bsd Sockets.” 2018. <a href="https://stackoverflow.com/a/13790353/2715565">https://stackoverflow.com/a/13790353/2715565</a>.</p>
</div>
<div id="ref-icdabgithub">
<p>“IOS Crash Dump Analysis Book Github Resources.” 2018. <a href="https://github.com/faisalmemon/ios-crash-dump-analysis-book">https://github.com/faisalmemon/ios-crash-dump-analysis-book</a>.</p>
</div>
<div id="ref-3rdpartycrashtools">
<p>“IOS Crash Reporting Tools.” 2017. <a href="https://rollout.io/blog/ios-crash-reporting-tools-2017-update/">https://rollout.io/blog/ios-crash-reporting-tools-2017-update/</a>.</p>
</div>
<div id="ref-wwdc2018_416">
<p>“IOS Memory Deep Dive.” 2018. <a href="https://devstreaming-cdn.apple.com/videos/wwdc/2018/416n2fmzz0fz88f/416/416_ios_memory_deep_dive.pdf?dl=1">https://devstreaming-cdn.apple.com/videos/wwdc/2018/416n2fmzz0fz88f/416/416_ios_memory_deep_dive.pdf?dl=1</a>.</p>
</div>
<div id="ref-libdispatchtar">
<p>“Libdispatch Open Source.” 2018. <a href="https://opensource.apple.com/tarballs/libdispatch/">https://opensource.apple.com/tarballs/libdispatch/</a>.</p>
</div>
<div id="ref-macherror">
<p>“Making Sense of I/O Kit Error Codes.” 2018. <a href="https://developer.apple.com/library/archive/qa/qa1075/_index.html">https://developer.apple.com/library/archive/qa/qa1075/_index.html</a>.</p>
</div>
<div id="ref-cfconstantstring">
<p>“NSCFConstantString in Corefoundation.framework.” 2018. <a href="https://github.com/JaviSoto/iOS10-Runtime-Headers/blob/master/Frameworks/CoreFoundation.framework/__NSCFConstantString.h">https://github.com/JaviSoto/iOS10-Runtime-Headers/blob/master/Frameworks/CoreFoundation.framework/__NSCFConstantString.h</a>.</p>
</div>
<div id="ref-dispatchdata">
<p>“NSDispatchData.h in Foundation.framework.” 2018. <a href="https://github.com/JaviSoto/iOS10-Runtime-Headers/blob/master/Frameworks/Foundation.framework/_NSDispatchData.h%0A">https://github.com/JaviSoto/iOS10-Runtime-Headers/blob/master/Frameworks/Foundation.framework/_NSDispatchData.h%0A</a>.</p>
</div>
<div id="ref-panicbook">
<p><em>Panic! Unix System Crash Dump Analysis</em>. 1995. 1st ed. Vol. 1. 0-13-149386-8. Prentice Hall.</p>
</div>
<div id="ref-plcrashreporter">
<p>“Plausible Labs Crash Reporter.” 2018. <a href="https://github.com/plausiblelabs/plcrashreporter">https://github.com/plausiblelabs/plcrashreporter</a>.</p>
</div>
<div id="ref-asanchecker">
<p>Serebryany, Konstantin, Derek Bruening, Alexander Potapenko, and Dmitry Vyukov. 2012. “AddressSanitizer: A Fast Address Sanity Checker.” In <em>USENIX Atc 2012</em>. <a href="https://www.usenix.org/conference/usenixfederatedconferencesweek/addresssanitizer-fast-address-sanity-checker">https://www.usenix.org/conference/usenixfederatedconferencesweek/addresssanitizer-fast-address-sanity-checker</a>.</p>
</div>
<div id="ref-threadstatus">
<p>“Thread Status Values in Mach.” 2018. <a href="https://github.com/apple/darwin-xnu/blob/0a798f6738bc1db01281fc08ae024145e84df927/osfmk/mach/arm/thread_status.h">https://github.com/apple/darwin-xnu/blob/0a798f6738bc1db01281fc08ae024145e84df927/osfmk/mach/arm/thread_status.h</a>.</p>
</div>
</div>
</body>
</html>
